diff --git a/Makefile b/Makefile index 6c63bf01..92596a45 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ black-check: poetry run black . --check mypy: - poetry run mypy cashu --ignore-missing + poetry run mypy cashu --ignore-missing --check-untyped-defs format: black ruff diff --git a/README.md b/README.md index 4ae71951..c359a570 100644 --- a/README.md +++ b/README.md @@ -140,7 +140,7 @@ This command will return a Lightning invoice that you need to pay to mint new ec cashu invoice 420 ``` -The client will check every few seconds if the invoice has been paid. If you abort this step but still pay the invoice, you can use the command `cashu invoice --hash `. +The client will check every few seconds if the invoice has been paid. If you abort this step but still pay the invoice, you can use the command `cashu invoice --id `. #### Pay a Lightning invoice ```bash diff --git a/cashu/core/base.py b/cashu/core/base.py index f2cbeb7a..061f34ab 100644 --- a/cashu/core/base.py +++ b/cashu/core/base.py @@ -88,10 +88,16 @@ class Proof(BaseModel): time_created: Union[None, str] = "" time_reserved: Union[None, str] = "" derivation_path: Union[None, str] = "" # derivation path of the proof + mint_id: Union[None, str] = ( + None # holds the id of the mint operation that created this proof + ) + melt_id: Union[None, str] = ( + None # holds the id of the melt operation that destroyed this proof + ) @classmethod def from_dict(cls, proof_dict: dict): - if proof_dict.get("dleq"): + if proof_dict.get("dleq") and isinstance(proof_dict["dleq"], str): proof_dict["dleq"] = DLEQWallet(**json.loads(proof_dict["dleq"])) c = cls(**proof_dict) return c @@ -181,8 +187,9 @@ class BlindedMessages(BaseModel): class Invoice(BaseModel): amount: int - pr: str - hash: str + bolt11: str + id: str + out: Union[None, bool] = None payment_hash: Union[None, str] = None preimage: Union[str, None] = None issued: Union[None, bool] = False diff --git a/cashu/core/bolt11.py b/cashu/core/bolt11.py deleted file mode 100644 index 3c59fbb5..00000000 --- a/cashu/core/bolt11.py +++ /dev/null @@ -1,369 +0,0 @@ -import hashlib -import re -import time -from binascii import unhexlify -from decimal import Decimal -from typing import List, NamedTuple, Optional - -import bitstring # type: ignore -import secp256k1 -from bech32 import CHARSET, bech32_decode, bech32_encode -from ecdsa import SECP256k1, VerifyingKey # type: ignore -from ecdsa.util import sigdecode_string # type: ignore - - -class Route(NamedTuple): - pubkey: str - short_channel_id: str - base_fee_msat: int - ppm_fee: int - cltv: int - - -class Invoice(object): - payment_hash: str - amount_msat: int = 0 - description: Optional[str] = None - description_hash: Optional[str] = None - payee: Optional[str] = None - date: int - expiry: int = 3600 - secret: Optional[str] = None - route_hints: List[Route] = [] - min_final_cltv_expiry: int = 18 - - -def decode(pr: str) -> Invoice: - """bolt11 decoder, - based on https://github.com/rustyrussell/lightning-payencode/blob/master/lnaddr.py - """ - - hrp, decoded_data = bech32_decode(pr) - if hrp is None or decoded_data is None: - raise ValueError("Bad bech32 checksum") - if not hrp.startswith("ln"): - raise ValueError("Does not start with ln") - - bitarray = _u5_to_bitarray(decoded_data) - - # final signature 65 bytes, split it off. - if len(bitarray) < 65 * 8: - raise ValueError("Too short to contain signature") - - # extract the signature - signature = bitarray[-65 * 8 :].tobytes() - - # the tagged fields as a bitstream - data = bitstring.ConstBitStream(bitarray[: -65 * 8]) - - # build the invoice object - invoice = Invoice() - - # decode the amount from the hrp - m = re.search(r"[^\d]+", hrp[2:]) - if m: - amountstr = hrp[2 + m.end() :] - if amountstr != "": - invoice.amount_msat = _unshorten_amount(amountstr) - - # pull out date - invoice.date = data.read(35).uint - - while data.pos != data.len: - tag, tagdata, data = _pull_tagged(data) - data_length = len(tagdata) / 5 - - if tag == "d": - invoice.description = _trim_to_bytes(tagdata).decode("utf-8") - elif tag == "h" and data_length == 52: - invoice.description_hash = _trim_to_bytes(tagdata).hex() - elif tag == "p" and data_length == 52: - invoice.payment_hash = _trim_to_bytes(tagdata).hex() - elif tag == "x": - invoice.expiry = tagdata.uint - elif tag == "n": - invoice.payee = _trim_to_bytes(tagdata).hex() - # this won't work in most cases, we must extract the payee - # from the signature - elif tag == "s": - invoice.secret = _trim_to_bytes(tagdata).hex() - elif tag == "r": - s = bitstring.ConstBitStream(tagdata) - while s.pos + 264 + 64 + 32 + 32 + 16 < s.len: - route = Route( - pubkey=s.read(264).tobytes().hex(), - short_channel_id=_readable_scid(s.read(64).intbe), - base_fee_msat=s.read(32).intbe, - ppm_fee=s.read(32).intbe, - cltv=s.read(16).intbe, - ) - invoice.route_hints.append(route) - - # BOLT #11: - # A reader MUST check that the `signature` is valid (see the `n` tagged - # field specified below). - # A reader MUST use the `n` field to validate the signature instead of - # performing signature recovery if a valid `n` field is provided. - message = bytearray([ord(c) for c in hrp]) + data.tobytes() - sig = signature[0:64] - if invoice.payee: - key = VerifyingKey.from_string(unhexlify(invoice.payee), curve=SECP256k1) - key.verify(sig, message, hashlib.sha256, sigdecode=sigdecode_string) - else: - keys = VerifyingKey.from_public_key_recovery( - sig, message, SECP256k1, hashlib.sha256 - ) - signaling_byte = signature[64] - key = keys[int(signaling_byte)] - invoice.payee = key.to_string("compressed").hex() - - return invoice - - -def encode(options): - """Convert options into LnAddr and pass it to the encoder""" - addr = LnAddr() - addr.currency = options["currency"] - addr.fallback = options["fallback"] if options["fallback"] else None - if options["amount"]: - addr.amount = options["amount"] - if options["timestamp"]: - addr.date = int(options["timestamp"]) - - addr.paymenthash = unhexlify(options["paymenthash"]) - - if options["description"]: - addr.tags.append(("d", options["description"])) - if options["description_hash"]: - addr.tags.append(("h", options["description_hash"])) - if options["expires"]: - addr.tags.append(("x", options["expires"])) - - if options["fallback"]: - addr.tags.append(("f", options["fallback"])) - if options["route"]: - for r in options["route"]: - splits = r.split("/") - route = [] - while len(splits) >= 5: - route.append( - ( - unhexlify(splits[0]), - unhexlify(splits[1]), - int(splits[2]), - int(splits[3]), - int(splits[4]), - ) - ) - splits = splits[5:] - assert len(splits) == 0 - addr.tags.append(("r", route)) - return lnencode(addr, options["privkey"]) - - -def lnencode(addr, privkey): - if addr.amount: - amount = Decimal(str(addr.amount)) - # We can only send down to millisatoshi. - if amount * 10**12 % 10: - raise ValueError( - "Cannot encode {}: too many decimal places".format(addr.amount) - ) - - amount = addr.currency + shorten_amount(amount) - else: - amount = addr.currency if addr.currency else "" - - hrp = "ln" + amount + "0n" - - # Start with the timestamp - data = bitstring.pack("uint:35", addr.date) - - # Payment hash - data += tagged_bytes("p", addr.paymenthash) - tags_set = set() - - for k, v in addr.tags: - # BOLT #11: - # - # A writer MUST NOT include more than one `d`, `h`, `n` or `x` fields, - if k in ("d", "h", "n", "x"): - if k in tags_set: - raise ValueError("Duplicate '{}' tag".format(k)) - - if k == "r": - route = bitstring.BitArray() - for step in v: - pubkey, channel, feebase, feerate, cltv = step - route.append( - bitstring.BitArray(pubkey) - + bitstring.BitArray(channel) - + bitstring.pack("intbe:32", feebase) - + bitstring.pack("intbe:32", feerate) - + bitstring.pack("intbe:16", cltv) - ) - data += tagged("r", route) - elif k == "f": - data += encode_fallback(v, addr.currency) - elif k == "d": - data += tagged_bytes("d", v.encode()) - elif k == "x": - # Get minimal length by trimming leading 5 bits at a time. - expirybits = bitstring.pack("intbe:64", v)[4:64] - while expirybits.startswith("0b00000"): - expirybits = expirybits[5:] - data += tagged("x", expirybits) - elif k == "h": - data += tagged_bytes("h", v) - elif k == "n": - data += tagged_bytes("n", v) - else: - # FIXME: Support unknown tags? - raise ValueError("Unknown tag {}".format(k)) - - tags_set.add(k) - - # BOLT #11: - # - # A writer MUST include either a `d` or `h` field, and MUST NOT include - # both. - if "d" in tags_set and "h" in tags_set: - raise ValueError("Cannot include both 'd' and 'h'") - if not "d" in tags_set and not "h" in tags_set: - raise ValueError("Must include either 'd' or 'h'") - - # We actually sign the hrp, then data (padded to 8 bits with zeroes). - privkey = secp256k1.PrivateKey(bytes(unhexlify(privkey))) - sig = privkey.ecdsa_sign_recoverable( - bytearray([ord(c) for c in hrp]) + data.tobytes() - ) - # This doesn't actually serialize, but returns a pair of values :( - sig, recid = privkey.ecdsa_recoverable_serialize(sig) - data += bytes(sig) + bytes([recid]) - - return bech32_encode(hrp, bitarray_to_u5(data)) - - -class LnAddr(object): - def __init__( - self, paymenthash=None, amount=None, currency="bc", tags=None, date=None - ): - self.date = int(time.time()) if not date else int(date) - self.tags = [] if not tags else tags - self.unknown_tags = [] - self.paymenthash = paymenthash - self.signature = None - self.pubkey = None - self.currency = currency - self.amount = amount - - def __str__(self): - return "LnAddr[{}, amount={}{} tags=[{}]]".format( - hexlify(self.pubkey.serialize()).decode("utf-8"), - self.amount, - self.currency, - ", ".join([k + "=" + str(v) for k, v in self.tags]), - ) - - -def shorten_amount(amount): - """Given an amount in bitcoin, shorten it""" - # Convert to pico initially - amount = int(amount * 10**12) - units = ["p", "n", "u", "m", ""] - for unit in units: - if amount % 1000 == 0: - amount //= 1000 - else: - break - return str(amount) + unit - - -def _unshorten_amount(amount: str) -> int: - """Given a shortened amount, return millisatoshis""" - # BOLT #11: - # The following `multiplier` letters are defined: - # - # * `m` (milli): multiply by 0.001 - # * `u` (micro): multiply by 0.000001 - # * `n` (nano): multiply by 0.000000001 - # * `p` (pico): multiply by 0.000000000001 - units = {"p": 10**12, "n": 10**9, "u": 10**6, "m": 10**3} - unit = str(amount)[-1] - - # BOLT #11: - # A reader SHOULD fail if `amount` contains a non-digit, or is followed by - # anything except a `multiplier` in the table above. - if not re.fullmatch(r"\d+[pnum]?", str(amount)): - raise ValueError("Invalid amount '{}'".format(amount)) - - if unit in units: - return int(int(amount[:-1]) * 100_000_000_000 / units[unit]) - else: - return int(amount) * 100_000_000_000 - - -def _pull_tagged(stream): - tag = stream.read(5).uint - length = stream.read(5).uint * 32 + stream.read(5).uint - return (CHARSET[tag], stream.read(length * 5), stream) - - -def is_p2pkh(currency, prefix): - return prefix == base58_prefix_map[currency][0] - - -def is_p2sh(currency, prefix): - return prefix == base58_prefix_map[currency][1] - - -# Tagged field containing BitArray -def tagged(char, l): - # Tagged fields need to be zero-padded to 5 bits. - while l.len % 5 != 0: - l.append("0b0") - return ( - bitstring.pack( - "uint:5, uint:5, uint:5", - CHARSET.find(char), - (l.len / 5) / 32, - (l.len / 5) % 32, - ) - + l - ) - - -def tagged_bytes(char, l): - return tagged(char, bitstring.BitArray(l)) - - -def _trim_to_bytes(barr): - # Adds a byte if necessary. - b = barr.tobytes() - if barr.len % 8 != 0: - return b[:-1] - return b - - -def _readable_scid(short_channel_id: int) -> str: - return "{blockheight}x{transactionindex}x{outputindex}".format( - blockheight=((short_channel_id >> 40) & 0xFFFFFF), - transactionindex=((short_channel_id >> 16) & 0xFFFFFF), - outputindex=(short_channel_id & 0xFFFF), - ) - - -def _u5_to_bitarray(arr: List[int]) -> bitstring.BitArray: - ret = bitstring.BitArray() - for a in arr: - ret += bitstring.pack("uint:5", a) - return ret - - -def bitarray_to_u5(barr): - assert barr.len % 5 == 0 - ret = [] - s = bitstring.ConstBitStream(barr) - while s.pos != s.len: - ret.append(s.read(5).uint) - return ret diff --git a/cashu/core/db.py b/cashu/core/db.py index 60873862..e2f99d88 100644 --- a/cashu/core/db.py +++ b/cashu/core/db.py @@ -3,7 +3,7 @@ import os import time from contextlib import asynccontextmanager -from typing import Optional +from typing import Optional, Union from sqlalchemy import create_engine from sqlalchemy_aio.base import AsyncConnection @@ -118,7 +118,7 @@ def _parse_timestamp(value, _): (1082, 1083, 1266), "DATE2INT", lambda value, curs: ( - time.mktime(value.timetuple()) if value is not None else None + time.mktime(value.timetuple()) if value is not None else None # type: ignore ), ) ) @@ -189,7 +189,7 @@ async def reuse_conn(self, conn: Connection): # public functions for LNbits to use (we don't want to change the Database or Compat classes above) -def table_with_schema(db: Database, table: str): +def table_with_schema(db: Union[Database, Connection], table: str): return f"{db.references_schema if db.schema else ''}{table}" diff --git a/cashu/core/helpers.py b/cashu/core/helpers.py index 15b77cd3..6b73d507 100644 --- a/cashu/core/helpers.py +++ b/cashu/core/helpers.py @@ -3,7 +3,7 @@ from functools import partial, wraps from typing import List -from ..core.base import Proof +from ..core.base import BlindedSignature, Proof from ..core.settings import settings @@ -11,6 +11,10 @@ def sum_proofs(proofs: List[Proof]): return sum([p.amount for p in proofs]) +def sum_promises(promises: List[BlindedSignature]): + return sum([p.amount for p in promises]) + + def async_wrap(func): @wraps(func) async def run(*args, loop=None, executor=None, **kwargs): diff --git a/cashu/core/legacy.py b/cashu/core/legacy.py index 9fc64b27..60535618 100644 --- a/cashu/core/legacy.py +++ b/cashu/core/legacy.py @@ -18,9 +18,9 @@ def hash_to_point_pre_0_3_3(secret_msg): _hash = hashlib.sha256(msg).hexdigest().encode("utf-8") # type: ignore try: # We construct compressed pub which has x coordinate encoded with even y - _hash = list(_hash[:33]) # take the 33 bytes and get a list of bytes - _hash[0] = 0x02 # set first byte to represent even y coord - _hash = bytes(_hash) + _hash_list = list(_hash[:33]) # take the 33 bytes and get a list of bytes + _hash_list[0] = 0x02 # set first byte to represent even y coord + _hash = bytes(_hash_list) point = PublicKey(_hash, raw=True) except Exception: msg = _hash diff --git a/cashu/core/split.py b/cashu/core/split.py index 79e75d03..8f90c732 100644 --- a/cashu/core/split.py +++ b/cashu/core/split.py @@ -1,4 +1,7 @@ -def amount_split(amount: int): +from typing import List + + +def amount_split(amount: int) -> List[int]: """Given an amount returns a list of amounts returned e.g. 13 is [1, 4, 8].""" bits_amt = bin(amount)[::-1][:-2] rv = [] diff --git a/cashu/lightning/base.py b/cashu/lightning/base.py index 7daa0353..17e681c8 100644 --- a/cashu/lightning/base.py +++ b/cashu/lightning/base.py @@ -1,29 +1,30 @@ from abc import ABC, abstractmethod -from typing import Coroutine, NamedTuple, Optional +from typing import Coroutine, Optional +from pydantic import BaseModel -class StatusResponse(NamedTuple): + +class StatusResponse(BaseModel): error_message: Optional[str] balance_msat: int -class InvoiceResponse(NamedTuple): - ok: bool - checking_id: Optional[str] = None # payment_hash, rpc_id +class InvoiceResponse(BaseModel): + ok: bool # True: invoice created, False: failed + checking_id: Optional[str] = None payment_request: Optional[str] = None error_message: Optional[str] = None -class PaymentResponse(NamedTuple): - # when ok is None it means we don't know if this succeeded - ok: Optional[bool] = None - checking_id: Optional[str] = None # payment_hash, rcp_id +class PaymentResponse(BaseModel): + ok: Optional[bool] = None # True: paid, False: failed, None: pending or unknown + checking_id: Optional[str] = None fee_msat: Optional[int] = None preimage: Optional[str] = None error_message: Optional[str] = None -class PaymentStatus(NamedTuple): +class PaymentStatus(BaseModel): paid: Optional[bool] = None fee_msat: Optional[int] = None preimage: Optional[str] = None diff --git a/cashu/lightning/fake.py b/cashu/lightning/fake.py index d2c617a5..ae956195 100644 --- a/cashu/lightning/fake.py +++ b/cashu/lightning/fake.py @@ -2,9 +2,18 @@ import hashlib import random from datetime import datetime -from typing import AsyncGenerator, Dict, Optional, Set +from os import urandom +from typing import AsyncGenerator, Optional, Set + +from bolt11 import ( + Bolt11, + MilliSatoshi, + TagChar, + Tags, + decode, + encode, +) -from ..core.bolt11 import Invoice, decode, encode from .base import ( InvoiceResponse, PaymentResponse, @@ -14,6 +23,8 @@ ) BRR = True +DELAY_PAYMENT = False +STOCHASTIC_INVOICE = False class FakeWallet(Wallet): @@ -31,7 +42,7 @@ class FakeWallet(Wallet): ).hex() async def status(self) -> StatusResponse: - return StatusResponse(None, 1337) + return StatusResponse(error_message=None, balance_msat=1337) async def create_invoice( self, @@ -39,65 +50,80 @@ async def create_invoice( memo: Optional[str] = None, description_hash: Optional[bytes] = None, unhashed_description: Optional[bytes] = None, - **kwargs, + expiry: Optional[int] = None, + payment_secret: Optional[bytes] = None, + **_, ) -> InvoiceResponse: - data: Dict = { - "out": False, - "amount": amount * 1000, - "currency": "bc", - "privkey": self.privkey, - "memo": memo, - "description_hash": b"", - "description": "", - "fallback": None, - "expires": kwargs.get("expiry"), - "timestamp": datetime.now().timestamp(), - "route": None, - "tags_set": [], - } + tags = Tags() + if description_hash: - data["tags_set"] = ["h"] - data["description_hash"] = description_hash + tags.add(TagChar.description_hash, description_hash.hex()) elif unhashed_description: - data["tags_set"] = ["d"] - data["description_hash"] = hashlib.sha256(unhashed_description).digest() + tags.add( + TagChar.description_hash, + hashlib.sha256(unhashed_description).hexdigest(), + ) else: - data["tags_set"] = ["d"] - data["memo"] = memo - data["description"] = memo - randomHash = ( + tags.add(TagChar.description, memo or "") + + if expiry: + tags.add(TagChar.expire_time, expiry) + + # random hash + checking_id = ( self.privkey[:6] + hashlib.sha256(str(random.getrandbits(256)).encode()).hexdigest()[6:] ) - data["paymenthash"] = randomHash - payment_request = encode(data) - checking_id = randomHash - return InvoiceResponse(True, checking_id, payment_request) + tags.add(TagChar.payment_hash, checking_id) + + if payment_secret: + secret = payment_secret.hex() + else: + secret = urandom(32).hex() + tags.add(TagChar.payment_secret, secret) + + bolt11 = Bolt11( + currency="bc", + amount_msat=MilliSatoshi(amount * 1000), + date=int(datetime.now().timestamp()), + tags=tags, + ) + + payment_request = encode(bolt11, self.privkey) + + return InvoiceResponse( + ok=True, checking_id=checking_id, payment_request=payment_request + ) async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse: invoice = decode(bolt11) - # await asyncio.sleep(5) + + if DELAY_PAYMENT: + await asyncio.sleep(5) if invoice.payment_hash[:6] == self.privkey[:6] or BRR: await self.queue.put(invoice) self.paid_invoices.add(invoice.payment_hash) - return PaymentResponse(True, invoice.payment_hash, 0) + return PaymentResponse( + ok=True, checking_id=invoice.payment_hash, fee_msat=0 + ) else: return PaymentResponse( ok=False, error_message="Only internal invoices can be used!" ) async def get_invoice_status(self, checking_id: str) -> PaymentStatus: - # paid = random.random() > 0.7 - # return PaymentStatus(paid) + if STOCHASTIC_INVOICE: + paid = random.random() > 0.7 + return PaymentStatus(paid=paid) paid = checking_id in self.paid_invoices or BRR - return PaymentStatus(paid or None) + return PaymentStatus(paid=paid or None) async def get_payment_status(self, _: str) -> PaymentStatus: - return PaymentStatus(None) + return PaymentStatus(paid=None) async def paid_invoices_stream(self) -> AsyncGenerator[str, None]: while True: - value: Invoice = await self.queue.get() + value: Bolt11 = await self.queue.get() yield value.payment_hash diff --git a/cashu/mint/crud.py b/cashu/mint/crud.py index 7faa1bc0..bbdf41b0 100644 --- a/cashu/mint/crud.py +++ b/cashu/mint/crud.py @@ -8,44 +8,155 @@ class LedgerCrud: """ Database interface for Cashu mint. - This class needs to be overloaded by any app that imports the Cashu mint. + This class needs to be overloaded by any app that imports the Cashu mint and wants + to use their own database. """ - async def get_keyset(*args, **kwags): - return await get_keyset(*args, **kwags) # type: ignore - - async def get_lightning_invoice(*args, **kwags): - return await get_lightning_invoice(*args, **kwags) # type: ignore - - async def get_secrets_used(*args, **kwags): - return await get_secrets_used(*args, **kwags) # type: ignore - - async def invalidate_proof(*args, **kwags): - return await invalidate_proof(*args, **kwags) # type: ignore - - async def get_proofs_pending(*args, **kwags): - return await get_proofs_pending(*args, **kwags) # type: ignore - - async def set_proof_pending(*args, **kwags): - return await set_proof_pending(*args, **kwags) # type: ignore - - async def unset_proof_pending(*args, **kwags): - return await unset_proof_pending(*args, **kwags) # type: ignore - - async def store_keyset(*args, **kwags): - return await store_keyset(*args, **kwags) # type: ignore - - async def store_lightning_invoice(*args, **kwags): - return await store_lightning_invoice(*args, **kwags) # type: ignore - - async def store_promise(*args, **kwags): - return await store_promise(*args, **kwags) # type: ignore - - async def get_promise(*args, **kwags): - return await get_promise(*args, **kwags) # type: ignore - - async def update_lightning_invoice(*args, **kwags): - return await update_lightning_invoice(*args, **kwags) # type: ignore + async def get_keyset( + self, + db: Database, + id: str = "", + derivation_path: str = "", + conn: Optional[Connection] = None, + ): + return await get_keyset( + db=db, + id=id, + derivation_path=derivation_path, + conn=conn, + ) + + async def get_lightning_invoice( + self, + db: Database, + id: str, + conn: Optional[Connection] = None, + ): + return await get_lightning_invoice( + db=db, + id=id, + conn=conn, + ) + + async def get_secrets_used( + self, + db: Database, + conn: Optional[Connection] = None, + ): + return await get_secrets_used(db=db, conn=conn) + + async def invalidate_proof( + self, + db: Database, + proof: Proof, + conn: Optional[Connection] = None, + ): + return await invalidate_proof( + db=db, + proof=proof, + conn=conn, + ) + + async def get_proofs_pending( + self, + db: Database, + conn: Optional[Connection] = None, + ): + return await get_proofs_pending(db=db, conn=conn) + + async def set_proof_pending( + self, + db: Database, + proof: Proof, + conn: Optional[Connection] = None, + ): + return await set_proof_pending( + db=db, + proof=proof, + conn=conn, + ) + + async def unset_proof_pending( + self, proof: Proof, db: Database, conn: Optional[Connection] = None + ): + return await unset_proof_pending( + proof=proof, + db=db, + conn=conn, + ) + + async def store_keyset( + self, + db: Database, + keyset: MintKeyset, + conn: Optional[Connection] = None, + ): + return await store_keyset( + db=db, + keyset=keyset, + conn=conn, + ) + + async def store_lightning_invoice( + self, + db: Database, + invoice: Invoice, + conn: Optional[Connection] = None, + ): + return await store_lightning_invoice( + db=db, + invoice=invoice, + conn=conn, + ) + + async def store_promise( + self, + *, + db: Database, + amount: int, + B_: str, + C_: str, + id: str, + e: str = "", + s: str = "", + conn: Optional[Connection] = None, + ): + return await store_promise( + db=db, + amount=amount, + B_=B_, + C_=C_, + id=id, + e=e, + s=s, + conn=conn, + ) + + async def get_promise( + self, + db: Database, + B_: str, + conn: Optional[Connection] = None, + ): + return await get_promise( + db=db, + B_=B_, + conn=conn, + ) + + async def update_lightning_invoice( + self, + db: Database, + id: str, + issued: bool, + conn: Optional[Connection] = None, + ): + return await update_lightning_invoice( + db=db, + id=id, + issued=issued, + conn=conn, + ) async def store_promise( @@ -174,46 +285,47 @@ async def store_lightning_invoice( await (conn or db).execute( f""" INSERT INTO {table_with_schema(db, 'invoices')} - (amount, pr, hash, issued, payment_hash) - VALUES (?, ?, ?, ?, ?) + (amount, bolt11, id, issued, payment_hash, out) + VALUES (?, ?, ?, ?, ?, ?) """, ( invoice.amount, - invoice.pr, - invoice.hash, + invoice.bolt11, + invoice.id, invoice.issued, invoice.payment_hash, + invoice.out, ), ) async def get_lightning_invoice( db: Database, - hash: str, + id: str, conn: Optional[Connection] = None, ): row = await (conn or db).fetchone( f""" SELECT * from {table_with_schema(db, 'invoices')} - WHERE hash = ? + WHERE id = ? """, - (hash,), + (id,), ) - - return Invoice(**row) if row else None + row_dict = dict(row) + return Invoice(**row_dict) if row_dict else None async def update_lightning_invoice( db: Database, - hash: str, + id: str, issued: bool, conn: Optional[Connection] = None, ): await (conn or db).execute( - f"UPDATE {table_with_schema(db, 'invoices')} SET issued = ? WHERE hash = ?", + f"UPDATE {table_with_schema(db, 'invoices')} SET issued = ? WHERE id = ?", ( issued, - hash, + id, ), ) diff --git a/cashu/mint/ledger.py b/cashu/mint/ledger.py index 8b1b8042..ed1a0329 100644 --- a/cashu/mint/ledger.py +++ b/cashu/mint/ledger.py @@ -1,10 +1,10 @@ import asyncio import math -from typing import Dict, List, Literal, Optional, Set, Tuple, Union +from typing import Dict, List, Optional, Set, Tuple +import bolt11 from loguru import logger -from ..core import bolt11 from ..core.base import ( DLEQ, BlindedMessage, @@ -19,7 +19,6 @@ from ..core.crypto.secp import PublicKey from ..core.db import Connection, Database from ..core.errors import ( - InvoiceNotPaidError, KeysetError, KeysetNotFoundError, LightningError, @@ -29,13 +28,14 @@ from ..core.helpers import fee_reserve, sum_proofs from ..core.settings import settings from ..core.split import amount_split -from ..lightning.base import Wallet +from ..lightning.base import PaymentResponse, Wallet from ..mint.crud import LedgerCrud from .conditions import LedgerSpendingConditions +from .lightning import LedgerLightning from .verification import LedgerVerification -class Ledger(LedgerVerification, LedgerSpendingConditions): +class Ledger(LedgerVerification, LedgerSpendingConditions, LedgerLightning): locks: Dict[str, asyncio.Lock] = {} # holds multiprocessing locks proofs_pending_lock: asyncio.Lock = ( asyncio.Lock() @@ -46,8 +46,8 @@ def __init__( db: Database, seed: str, lightning: Wallet, + crud: LedgerCrud, derivation_path="", - crud=LedgerCrud, ): self.secrets_used: Set[str] = set() self.master_key = seed @@ -146,113 +146,6 @@ def get_keyset(self, keyset_id: Optional[str] = None) -> Dict[int, str]: assert keyset.public_keys, KeysetError("no public keys for this keyset") return {a: p.serialize().hex() for a, p in keyset.public_keys.items()} - # ------- LIGHTNING ------- - - async def _request_lightning_invoice(self, amount: int) -> Tuple[str, str]: - """Generate a Lightning invoice using the funding source backend. - - Args: - amount (int): Amount of invoice (in Satoshis) - - Raises: - Exception: Error with funding source. - - Returns: - Tuple[str, str]: Bolt11 invoice and payment hash (for lookup) - """ - logger.trace( - "_request_lightning_invoice: Requesting Lightning invoice for" - f" {amount} satoshis." - ) - error, balance = await self.lightning.status() - logger.trace(f"_request_lightning_invoice: Lightning wallet balance: {balance}") - if error: - raise LightningError(f"Lightning wallet not responding: {error}") - ( - ok, - checking_id, - payment_request, - error_message, - ) = await self.lightning.create_invoice(amount, "Cashu deposit") - logger.trace( - f"_request_lightning_invoice: Lightning invoice: {payment_request}" - ) - - if not ok: - raise LightningError(f"Lightning wallet error: {error_message}") - assert payment_request and checking_id, LightningError( - "could not fetch invoice from Lightning backend" - ) - return payment_request, checking_id - - async def _check_lightning_invoice( - self, amount: int, hash: str, conn: Optional[Connection] = None - ) -> Literal[True]: - """Checks with the Lightning backend whether an invoice stored with `hash` was paid. - - Args: - amount (int): Amount of the outputs the wallet wants in return (in Satoshis). - hash (str): Hash to look up Lightning invoice by. - - Raises: - Exception: Invoice not found. - Exception: Tokens for invoice already issued. - Exception: Amount larger than invoice amount. - Exception: Invoice not paid yet - e: Update database and pass through error. - - Returns: - bool: True if invoice has been paid, else False - """ - invoice: Union[Invoice, None] = await self.crud.get_lightning_invoice( - hash=hash, db=self.db, conn=conn - ) - if invoice is None: - raise LightningError("invoice not found.") - if invoice.issued: - raise LightningError("tokens already issued for this invoice.") - if amount > invoice.amount: - raise LightningError( - f"requested amount too high: {amount}. Invoice amount: {invoice.amount}" - ) - assert invoice.payment_hash, "invoice has no payment hash." - status = await self.lightning.get_invoice_status(invoice.payment_hash) - logger.trace( - f"_check_lightning_invoice: invoice {invoice.payment_hash} status: {status}" - ) - if not status.paid: - raise InvoiceNotPaidError() - - return status.paid - - async def _pay_lightning_invoice(self, invoice: str, fee_limit_msat: int): - """Pays a Lightning invoice via the funding source backend. - - Args: - invoice (str): Bolt11 Lightning invoice - fee_limit_msat (int): Maximum fee reserve for payment (in Millisatoshi) - - Raises: - Exception: Funding source error. - - Returns: - Tuple[bool, string, int]: Returns payment status, preimage of invoice, paid fees (in Millisatoshi) - """ - error, balance = await self.lightning.status() - if error: - raise LightningError(f"Lightning wallet not responding: {error}") - ( - ok, - checking_id, - fee_msat, - preimage, - error_message, - ) = await self.lightning.pay_invoice(invoice, fee_limit_msat=fee_limit_msat) - logger.trace(f"_pay_lightning_invoice: Lightning payment status: {ok}") - # make sure that fee is positive - fee_msat = abs(fee_msat) if fee_msat else fee_msat - return ok, preimage, fee_msat - # ------- ECASH ------- async def _invalidate_proofs(self, proofs: List[Proof]) -> None: @@ -343,7 +236,7 @@ async def request_mint(self, amount: int) -> Tuple[str, str]: Exception: Invoice creation failed. Returns: - Tuple[str, str]: Bolt11 invoice and a hash (for looking it up later) + Tuple[str, str]: Bolt11 invoice and a id (for looking it up later) """ logger.trace("called request_mint") if settings.mint_max_peg_in and amount > settings.mint_max_peg_in: @@ -354,40 +247,43 @@ async def request_mint(self, amount: int) -> Tuple[str, str]: raise NotAllowedError("Mint does not allow minting new tokens.") logger.trace(f"requesting invoice for {amount} satoshis") - payment_request, payment_hash = await self._request_lightning_invoice(amount) - logger.trace(f"got invoice {payment_request} with hash {payment_hash}") - assert payment_request and payment_hash, LightningError( - "could not fetch invoice from Lightning backend" + invoice_response = await self._request_lightning_invoice(amount) + logger.trace( + f"got invoice {invoice_response.payment_request} with check id" + f" {invoice_response.checking_id}" ) + assert ( + invoice_response.payment_request and invoice_response.checking_id + ), LightningError("could not fetch invoice from Lightning backend") invoice = Invoice( amount=amount, - hash=random_hash(), - pr=payment_request, - payment_hash=payment_hash, # what we got from the backend + id=random_hash(), + bolt11=invoice_response.payment_request, + payment_hash=invoice_response.checking_id, # what we got from the backend issued=False, ) - logger.trace(f"crud: storing invoice {invoice.hash} in db") + logger.trace(f"crud: storing invoice {invoice.id} in db") await self.crud.store_lightning_invoice(invoice=invoice, db=self.db) - logger.trace(f"crud: stored invoice {invoice.hash} in db") - return payment_request, invoice.hash + logger.trace(f"crud: stored invoice {invoice.id} in db") + return invoice_response.payment_request, invoice.id async def mint( self, B_s: List[BlindedMessage], - hash: Optional[str] = None, + id: Optional[str] = None, keyset: Optional[MintKeyset] = None, ) -> List[BlindedSignature]: """Mints a promise for coins for B_. Args: B_s (List[BlindedMessage]): Outputs (blinded messages) to sign. - hash (Optional[str], optional): Hash of (paid) Lightning invoice. Defaults to None. + id (Optional[str], optional): Id of (paid) Lightning invoice. Defaults to None. keyset (Optional[MintKeyset], optional): Keyset to use. If not provided, uses active keyset. Defaults to None. Raises: - Exception: Lightning invvoice is not paid. - Exception: Lightning is turned on but no payment hash is provided. + Exception: Lightning invoice is not paid. + Exception: Lightning is turned on but no id is provided. Exception: Something went wrong with the invoice check. Exception: Amount too large. @@ -398,21 +294,19 @@ async def mint( amount_outputs = sum([b.amount for b in B_s]) if settings.lightning: - if not hash: - raise NotAllowedError("no hash provided.") - self.locks[hash] = ( - self.locks.get(hash) or asyncio.Lock() + if not id: + raise NotAllowedError("no id provided.") + self.locks[id] = ( + self.locks.get(id) or asyncio.Lock() ) # create a new lock if it doesn't exist - async with self.locks[hash]: + async with self.locks[id]: # will raise an exception if the invoice is not paid or tokens are # already issued or the requested amount is too high - await self._check_lightning_invoice(amount_outputs, hash) + await self._check_lightning_invoice(amount=amount_outputs, id=id) - logger.trace(f"crud: setting invoice {hash} as issued") - await self.crud.update_lightning_invoice( - hash=hash, issued=True, db=self.db - ) - del self.locks[hash] + logger.trace(f"crud: setting invoice {id} as issued") + await self.crud.update_lightning_invoice(id=id, issued=True, db=self.db) + del self.locks[id] self._verify_outputs(B_s) @@ -439,6 +333,7 @@ async def melt( logger.trace("melt called") + # set proofs to pending to avoid race conditions await self._set_proofs_pending(proofs) try: @@ -465,20 +360,19 @@ async def melt( if settings.lightning: logger.trace(f"paying lightning invoice {invoice}") - status, preimage, paid_fee_msat = await self._pay_lightning_invoice( + payment = await self._pay_lightning_invoice( invoice, reserve_fees_sat * 1000 ) - preimage = preimage or "" logger.trace("paid lightning invoice") else: - status, preimage, paid_fee_msat = True, "preimage", 0 + payment = PaymentResponse(ok=True, preimage="preimage", fee_msat=0) logger.debug( - f"Melt status: {status}: preimage: {preimage}, fee_msat:" - f" {paid_fee_msat}" + f"Melt status: {payment.ok}: preimage: {payment.preimage}, fee_msat:" + f" {payment.fee_msat}" ) - if not status: + if not payment.ok: raise LightningError("Lightning payment unsuccessful.") # melt successful, invalidate proofs @@ -486,11 +380,11 @@ async def melt( # prepare change to compensate wallet for overpaid fees return_promises: List[BlindedSignature] = [] - if outputs and paid_fee_msat is not None: + if outputs and payment.fee_msat is not None: return_promises = await self._generate_change_promises( total_provided=total_provided, invoice_amount=invoice_amount, - ln_fee_msat=paid_fee_msat, + ln_fee_msat=payment.fee_msat, outputs=outputs, ) @@ -501,7 +395,7 @@ async def melt( # delete proofs from pending list await self._unset_proofs_pending(proofs) - return status, preimage, return_promises + return payment.ok, payment.preimage or "", return_promises async def get_melt_fees(self, pr: str) -> int: """Returns the fee reserve (in sat) that a wallet must add to its proofs @@ -515,19 +409,24 @@ async def get_melt_fees(self, pr: str) -> int: """ # hack: check if it's internal, if it exists, it will return paid = False, # if id does not exist (not internal), it returns paid = None + amount_msat = 0 if settings.lightning: decoded_invoice = bolt11.decode(pr) - amount_msat = decoded_invoice.amount_msat + assert decoded_invoice.amount_msat, "invoice has no amount." + amount_msat = int(decoded_invoice.amount_msat) logger.trace( "get_melt_fees: checking lightning invoice:" f" {decoded_invoice.payment_hash}" ) - paid = await self.lightning.get_invoice_status(decoded_invoice.payment_hash) - logger.trace(f"get_melt_fees: paid: {paid}") - internal = paid.paid is False + payment = await self.lightning.get_invoice_status( + decoded_invoice.payment_hash + ) + logger.trace(f"get_melt_fees: paid: {payment.paid}") + internal = payment.paid is False else: amount_msat = 0 internal = True + fees_msat = fee_reserve(amount_msat, internal) fee_sat = math.ceil(fees_msat / 1000) return fee_sat @@ -732,7 +631,7 @@ async def _set_proofs_pending( Raises: Exception: At least one proof already in pending table. """ - # first we check whether these proofs are pending aready + # first we check whether these proofs are pending already async with self.proofs_pending_lock: await self._validate_proofs_pending(proofs, conn) for p in proofs: diff --git a/cashu/mint/lightning.py b/cashu/mint/lightning.py new file mode 100644 index 00000000..076c714c --- /dev/null +++ b/cashu/mint/lightning.py @@ -0,0 +1,137 @@ +from typing import Optional, Union + +from loguru import logger + +from ..core.base import ( + Invoice, +) +from ..core.db import Connection, Database +from ..core.errors import ( + InvoiceNotPaidError, + LightningError, +) +from ..lightning.base import InvoiceResponse, PaymentResponse, PaymentStatus, Wallet +from ..mint.crud import LedgerCrud +from .protocols import SupportLightning, SupportsDb + + +class LedgerLightning(SupportLightning, SupportsDb): + """Lightning functions for the ledger.""" + + lightning: Wallet + crud: LedgerCrud + db: Database + + async def _request_lightning_invoice(self, amount: int) -> InvoiceResponse: + """Generate a Lightning invoice using the funding source backend. + + Args: + amount (int): Amount of invoice (in Satoshis) + + Raises: + Exception: Error with funding source. + + Returns: + Tuple[str, str]: Bolt11 invoice and payment id (for lookup) + """ + logger.trace( + "_request_lightning_invoice: Requesting Lightning invoice for" + f" {amount} satoshis." + ) + status = await self.lightning.status() + logger.trace( + "_request_lightning_invoice: Lightning wallet balance:" + f" {status.balance_msat}" + ) + if status.error_message: + raise LightningError( + f"Lightning wallet not responding: {status.error_message}" + ) + payment = await self.lightning.create_invoice(amount, "Cashu deposit") + logger.trace( + f"_request_lightning_invoice: Lightning invoice: {payment.payment_request}" + ) + + if not payment.ok: + raise LightningError(f"Lightning wallet error: {payment.error_message}") + assert payment.payment_request and payment.checking_id, LightningError( + "could not fetch invoice from Lightning backend" + ) + return payment + + async def _check_lightning_invoice( + self, *, amount: int, id: str, conn: Optional[Connection] = None + ) -> PaymentStatus: + """Checks with the Lightning backend whether an invoice with `id` was paid. + + Args: + amount (int): Amount of the outputs the wallet wants in return (in Satoshis). + id (str): Id to look up Lightning invoice by. + + Raises: + Exception: Invoice not found. + Exception: Tokens for invoice already issued. + Exception: Amount larger than invoice amount. + Exception: Invoice not paid yet + e: Update database and pass through error. + + Returns: + bool: True if invoice has been paid, else False + """ + invoice: Union[Invoice, None] = await self.crud.get_lightning_invoice( + id=id, db=self.db, conn=conn + ) + if invoice is None: + raise LightningError("invoice not found.") + if invoice.issued: + raise LightningError("tokens already issued for this invoice.") + if amount > invoice.amount: + raise LightningError( + f"requested amount too high: {amount}. Invoice amount: {invoice.amount}" + ) + assert invoice.payment_hash, "invoice has no payment hash." + # set this invoice as issued + await self.crud.update_lightning_invoice( + id=id, issued=True, db=self.db, conn=conn + ) + + try: + status = await self.lightning.get_invoice_status(invoice.payment_hash) + if status.paid: + return status + else: + raise InvoiceNotPaidError() + except Exception as e: + # unset issued + await self.crud.update_lightning_invoice( + id=id, issued=False, db=self.db, conn=conn + ) + raise e + + async def _pay_lightning_invoice( + self, invoice: str, fee_limit_msat: int + ) -> PaymentResponse: + """Pays a Lightning invoice via the funding source backend. + + Args: + invoice (str): Bolt11 Lightning invoice + fee_limit_msat (int): Maximum fee reserve for payment (in Millisatoshi) + + Raises: + Exception: Funding source error. + + Returns: + Tuple[bool, string, int]: Returns payment status, preimage of invoice, paid fees (in Millisatoshi) + """ + status = await self.lightning.status() + if status.error_message: + raise LightningError( + f"Lightning wallet not responding: {status.error_message}" + ) + payment = await self.lightning.pay_invoice( + invoice, fee_limit_msat=fee_limit_msat + ) + logger.trace(f"_pay_lightning_invoice: Lightning payment status: {payment.ok}") + # make sure that fee is positive and not None + payment.fee_msat = abs(payment.fee_msat) if payment.fee_msat else 0 + return payment diff --git a/cashu/mint/migrations.py b/cashu/mint/migrations.py index 0d4df023..c2c7a937 100644 --- a/cashu/mint/migrations.py +++ b/cashu/mint/migrations.py @@ -1,9 +1,9 @@ -from ..core.db import Database, table_with_schema +from ..core.db import Connection, Database, table_with_schema -async def m000_create_migrations_table(db: Database): - await db.execute(f""" - CREATE TABLE IF NOT EXISTS {table_with_schema(db, 'dbversions')} ( +async def m000_create_migrations_table(conn: Connection): + await conn.execute(f""" + CREATE TABLE IF NOT EXISTS {table_with_schema(conn, 'dbversions')} ( db TEXT PRIMARY KEY, version INT NOT NULL ) @@ -11,120 +11,127 @@ async def m000_create_migrations_table(db: Database): async def m001_initial(db: Database): - await db.execute(f""" - CREATE TABLE IF NOT EXISTS {table_with_schema(db, 'promises')} ( - amount {db.big_int} NOT NULL, - B_b TEXT NOT NULL, - C_b TEXT NOT NULL, - - UNIQUE (B_b) - - ); + async with db.connect() as conn: + await conn.execute(f""" + CREATE TABLE IF NOT EXISTS {table_with_schema(db, 'promises')} ( + amount {db.big_int} NOT NULL, + B_b TEXT NOT NULL, + C_b TEXT NOT NULL, + + UNIQUE (B_b) + + ); + """) + + await conn.execute(f""" + CREATE TABLE IF NOT EXISTS {table_with_schema(db, 'proofs_used')} ( + amount {db.big_int} NOT NULL, + C TEXT NOT NULL, + secret TEXT NOT NULL, + + UNIQUE (secret) + + ); + """) + + await conn.execute(f""" + CREATE TABLE IF NOT EXISTS {table_with_schema(db, 'invoices')} ( + amount {db.big_int} NOT NULL, + pr TEXT NOT NULL, + hash TEXT NOT NULL, + issued BOOL NOT NULL, + + UNIQUE (hash) + + ); + """) + + +async def m002_add_balance_views(db: Database): + async with db.connect() as conn: + await conn.execute(f""" + CREATE VIEW {table_with_schema(db, 'balance_issued')} AS + SELECT COALESCE(SUM(s), 0) AS balance FROM ( + SELECT SUM(amount) + FROM {table_with_schema(db, 'promises')} + WHERE amount > 0 + ) AS s; """) - await db.execute(f""" - CREATE TABLE IF NOT EXISTS {table_with_schema(db, 'proofs_used')} ( - amount {db.big_int} NOT NULL, - C TEXT NOT NULL, - secret TEXT NOT NULL, - - UNIQUE (secret) - - ); + await conn.execute(f""" + CREATE VIEW {table_with_schema(db, 'balance_redeemed')} AS + SELECT COALESCE(SUM(s), 0) AS balance FROM ( + SELECT SUM(amount) + FROM {table_with_schema(db, 'proofs_used')} + WHERE amount > 0 + ) AS s; """) - await db.execute(f""" - CREATE TABLE IF NOT EXISTS {table_with_schema(db, 'invoices')} ( - amount {db.big_int} NOT NULL, - pr TEXT NOT NULL, - hash TEXT NOT NULL, - issued BOOL NOT NULL, - - UNIQUE (hash) - - ); + await conn.execute(f""" + CREATE VIEW {table_with_schema(db, 'balance')} AS + SELECT s_issued - s_used FROM ( + SELECT bi.balance AS s_issued, bu.balance AS s_used + FROM {table_with_schema(db, 'balance_issued')} bi + CROSS JOIN {table_with_schema(db, 'balance_redeemed')} bu + ) AS balance; """) - await db.execute(f""" - CREATE VIEW {table_with_schema(db, 'balance_issued')} AS - SELECT COALESCE(SUM(s), 0) AS balance FROM ( - SELECT SUM(amount) - FROM {table_with_schema(db, 'promises')} - WHERE amount > 0 - ) AS s; - """) - - await db.execute(f""" - CREATE VIEW {table_with_schema(db, 'balance_redeemed')} AS - SELECT COALESCE(SUM(s), 0) AS balance FROM ( - SELECT SUM(amount) - FROM {table_with_schema(db, 'proofs_used')} - WHERE amount > 0 - ) AS s; - """) - - await db.execute(f""" - CREATE VIEW {table_with_schema(db, 'balance')} AS - SELECT s_issued - s_used FROM ( - SELECT bi.balance AS s_issued, bu.balance AS s_used - FROM {table_with_schema(db, 'balance_issued')} bi - CROSS JOIN {table_with_schema(db, 'balance_redeemed')} bu - ) AS balance; - """) - async def m003_mint_keysets(db: Database): """ Stores mint keysets from different mints and epochs. """ - await db.execute(f""" - CREATE TABLE IF NOT EXISTS {table_with_schema(db, 'keysets')} ( - id TEXT NOT NULL, - derivation_path TEXT, - valid_from TIMESTAMP NOT NULL DEFAULT {db.timestamp_now}, - valid_to TIMESTAMP NOT NULL DEFAULT {db.timestamp_now}, - first_seen TIMESTAMP NOT NULL DEFAULT {db.timestamp_now}, - active BOOL DEFAULT TRUE, - - UNIQUE (derivation_path) - - ); - """) - await db.execute(f""" - CREATE TABLE IF NOT EXISTS {table_with_schema(db, 'mint_pubkeys')} ( - id TEXT NOT NULL, - amount INTEGER NOT NULL, - pubkey TEXT NOT NULL, + async with db.connect() as conn: + await conn.execute(f""" + CREATE TABLE IF NOT EXISTS {table_with_schema(db, 'keysets')} ( + id TEXT NOT NULL, + derivation_path TEXT, + valid_from TIMESTAMP NOT NULL DEFAULT {db.timestamp_now}, + valid_to TIMESTAMP NOT NULL DEFAULT {db.timestamp_now}, + first_seen TIMESTAMP NOT NULL DEFAULT {db.timestamp_now}, + active BOOL DEFAULT TRUE, - UNIQUE (id, pubkey) + UNIQUE (derivation_path) - ); - """) + ); + """) + await conn.execute(f""" + CREATE TABLE IF NOT EXISTS {table_with_schema(db, 'mint_pubkeys')} ( + id TEXT NOT NULL, + amount INTEGER NOT NULL, + pubkey TEXT NOT NULL, + + UNIQUE (id, pubkey) + + ); + """) async def m004_keysets_add_version(db: Database): """ Column that remembers with which version """ - await db.execute( - f"ALTER TABLE {table_with_schema(db, 'keysets')} ADD COLUMN version TEXT" - ) + async with db.connect() as conn: + await conn.execute( + f"ALTER TABLE {table_with_schema(db, 'keysets')} ADD COLUMN version TEXT" + ) async def m005_pending_proofs_table(db: Database) -> None: """ Store pending proofs. """ - await db.execute(f""" - CREATE TABLE IF NOT EXISTS {table_with_schema(db, 'proofs_pending')} ( - amount INTEGER NOT NULL, - C TEXT NOT NULL, - secret TEXT NOT NULL, + async with db.connect() as conn: + await conn.execute(f""" + CREATE TABLE IF NOT EXISTS {table_with_schema(db, 'proofs_pending')} ( + amount INTEGER NOT NULL, + C TEXT NOT NULL, + secret TEXT NOT NULL, - UNIQUE (secret) + UNIQUE (secret) - ); - """) + ); + """) async def m006_invoices_add_payment_hash(db: Database): @@ -133,38 +140,67 @@ async def m006_invoices_add_payment_hash(db: Database): the column hash as a random identifier now (see https://github.com/cashubtc/nuts/pull/14). """ - await db.execute( - f"ALTER TABLE {table_with_schema(db, 'invoices')} ADD COLUMN payment_hash TEXT" - ) - await db.execute( - f"UPDATE {table_with_schema(db, 'invoices')} SET payment_hash = hash" - ) + async with db.connect() as conn: + await conn.execute( + f"ALTER TABLE {table_with_schema(db, 'invoices')} ADD COLUMN payment_hash" + " TEXT" + ) + await conn.execute( + f"UPDATE {table_with_schema(db, 'invoices')} SET payment_hash = hash" + ) async def m007_proofs_and_promises_store_id(db: Database): """ - Column that remembers the payment_hash as we're using - the column hash as a random identifier now - (see https://github.com/cashubtc/nuts/pull/14). + Column that stores the id of the proof or promise. """ - await db.execute( - f"ALTER TABLE {table_with_schema(db, 'proofs_used')} ADD COLUMN id TEXT" - ) - await db.execute( - f"ALTER TABLE {table_with_schema(db, 'proofs_pending')} ADD COLUMN id TEXT" - ) - await db.execute( - f"ALTER TABLE {table_with_schema(db, 'promises')} ADD COLUMN id TEXT" - ) + async with db.connect() as conn: + await conn.execute( + f"ALTER TABLE {table_with_schema(db, 'proofs_used')} ADD COLUMN id TEXT" + ) + await conn.execute( + f"ALTER TABLE {table_with_schema(db, 'proofs_pending')} ADD COLUMN id TEXT" + ) + await conn.execute( + f"ALTER TABLE {table_with_schema(db, 'promises')} ADD COLUMN id TEXT" + ) async def m008_promises_dleq(db: Database): """ Add columns for DLEQ proof to promises table. """ - await db.execute( - f"ALTER TABLE {table_with_schema(db, 'promises')} ADD COLUMN e TEXT" - ) - await db.execute( - f"ALTER TABLE {table_with_schema(db, 'promises')} ADD COLUMN s TEXT" - ) + async with db.connect() as conn: + await conn.execute( + f"ALTER TABLE {table_with_schema(db, 'promises')} ADD COLUMN e TEXT" + ) + await conn.execute( + f"ALTER TABLE {table_with_schema(db, 'promises')} ADD COLUMN s TEXT" + ) + + +async def m009_add_out_to_invoices(db: Database): + # column in invoices for marking whether the invoice is incoming (out=False) or outgoing (out=True) + async with db.connect() as conn: + # we have to drop the balance views first and recreate them later + await conn.execute(f"DROP VIEW {table_with_schema(db, 'balance_issued')}") + await conn.execute(f"DROP VIEW {table_with_schema(db, 'balance_redeemed')}") + await conn.execute(f"DROP VIEW {table_with_schema(db, 'balance')}") + + # rename column pr to bolt11 + await conn.execute( + f"ALTER TABLE {table_with_schema(db, 'invoices')} RENAME COLUMN pr TO" + " bolt11" + ) + # rename column hash to payment_hash + await conn.execute( + f"ALTER TABLE {table_with_schema(db, 'invoices')} RENAME COLUMN hash TO id" + ) + + # recreate balance views + await m002_add_balance_views(db) + + async with db.connect() as conn: + await conn.execute( + f"ALTER TABLE {table_with_schema(db, 'invoices')} ADD COLUMN out BOOL" + ) diff --git a/cashu/mint/protocols.py b/cashu/mint/protocols.py index 947849e9..bef454a4 100644 --- a/cashu/mint/protocols.py +++ b/cashu/mint/protocols.py @@ -1,8 +1,20 @@ from typing import Protocol from ..core.base import MintKeyset, MintKeysets +from ..core.db import Database +from ..lightning.base import Wallet +from ..mint.crud import LedgerCrud class SupportsKeysets(Protocol): keyset: MintKeyset keysets: MintKeysets + + +class SupportLightning(Protocol): + lightning: Wallet + + +class SupportsDb(Protocol): + db: Database + crud: LedgerCrud diff --git a/cashu/mint/router.py b/cashu/mint/router.py index 8b00d0c1..b556954b 100644 --- a/cashu/mint/router.py +++ b/cashu/mint/router.py @@ -162,10 +162,10 @@ async def mint( # BEGIN: backwards compatibility < 0.12 where we used to lookup payments with payment_hash # We use the payment_hash to lookup the hash from the database and pass that one along. - hash = payment_hash or hash + id = payment_hash or hash # END: backwards compatibility < 0.12 - promises = await ledger.mint(payload.outputs, hash=hash) + promises = await ledger.mint(payload.outputs, id=id) blinded_signatures = PostMintResponse(promises=promises) logger.trace(f"< POST /mint: {blinded_signatures}") return blinded_signatures diff --git a/cashu/mint/startup.py b/cashu/mint/startup.py index 735f59e9..162c853f 100644 --- a/cashu/mint/startup.py +++ b/cashu/mint/startup.py @@ -10,6 +10,7 @@ from ..core.migrations import migrate_databases from ..core.settings import settings from ..mint import migrations +from ..mint.crud import LedgerCrud from ..mint.ledger import Ledger logger.debug("Enviroment Settings:") @@ -26,6 +27,7 @@ seed=settings.mint_private_key, derivation_path=settings.mint_derivation_path, lightning=lightning_backend, + crud=LedgerCrud(), ) @@ -50,14 +52,14 @@ async def start_mint_init(): if settings.lightning: logger.info(f"Using backend: {settings.mint_lightning_backend}") - error_message, balance = await ledger.lightning.status() - if error_message: + status = await ledger.lightning.status() + if status.error_message: logger.warning( - f"The backend for {ledger.lightning.__class__.__name__} isn't working" - f" properly: '{error_message}'", + f"The backend for {ledger.lightning.__class__.__name__} isn't" + f" working properly: '{status.error_message}'", RuntimeWarning, ) - logger.info(f"Lightning balance: {balance} msat") + logger.info(f"Lightning balance: {status.balance_msat} msat") logger.info(f"Data dir: {settings.cashu_dir}") logger.info("Mint started.") diff --git a/cashu/wallet/api/responses.py b/cashu/wallet/api/responses.py index b77d198d..db3532e0 100644 --- a/cashu/wallet/api/responses.py +++ b/cashu/wallet/api/responses.py @@ -6,15 +6,13 @@ class PayResponse(BaseModel): - amount: int - fee: int - amount_with_fee: int + ok: Optional[bool] = None class InvoiceResponse(BaseModel): amount: Optional[int] = None invoice: Optional[Invoice] = None - hash: Optional[str] = None + id: Optional[str] = None class SwapResponse(BaseModel): diff --git a/cashu/wallet/api/router.py b/cashu/wallet/api/router.py index f7fd0d4c..22d46ab9 100644 --- a/cashu/wallet/api/router.py +++ b/cashu/wallet/api/router.py @@ -11,6 +11,12 @@ from ...core.base import TokenV3 from ...core.helpers import sum_proofs from ...core.settings import settings +from ...lightning.base import ( + InvoiceResponse, + PaymentResponse, + PaymentStatus, + StatusResponse, +) from ...nostr.client.client import NostrClient from ...tor.tor import TorProxy from ...wallet.crud import get_lightning_invoices, get_reserved_proofs @@ -23,16 +29,15 @@ ) from ...wallet.nostr import receive_nostr, send_nostr from ...wallet.wallet import Wallet as Wallet +from ..lightning.lightning import LightningWallet from .api_helpers import verify_mints from .responses import ( BalanceResponse, BurnResponse, InfoResponse, - InvoiceResponse, InvoicesResponse, LockResponse, LocksResponse, - PayResponse, PendingResponse, ReceiveResponse, RestoreResponse, @@ -44,17 +49,19 @@ router: APIRouter = APIRouter() -async def mint_wallet(mint_url: Optional[str] = None): - wallet: Wallet = await Wallet.with_db( +async def mint_wallet( + mint_url: Optional[str] = None, raise_connection_error: bool = True +) -> LightningWallet: + lightning_wallet = await LightningWallet.with_db( mint_url or settings.mint_url, db=os.path.join(settings.cashu_dir, settings.wallet_name), name=settings.wallet_name, ) - await wallet.load_mint() - return wallet + await lightning_wallet.async_init(raise_connection_error=raise_connection_error) + return lightning_wallet -wallet: Wallet = Wallet( +wallet = LightningWallet( settings.mint_url, db=os.path.join(settings.cashu_dir, settings.wallet_name), name=settings.wallet_name, @@ -64,87 +71,101 @@ async def mint_wallet(mint_url: Optional[str] = None): @router.on_event("startup") async def start_wallet(): global wallet - wallet = await Wallet.with_db( - settings.mint_url, - db=os.path.join(settings.cashu_dir, settings.wallet_name), - name=settings.wallet_name, - ) - + wallet = await mint_wallet(settings.mint_url, raise_connection_error=False) if settings.tor and not TorProxy().check_platform(): raise Exception("tor not working.") - await wallet.load_mint() -@router.post("/pay", name="Pay lightning invoice", response_model=PayResponse) +@router.post( + "/lightning/pay_invoice", + name="Pay lightning invoice", + response_model=PaymentResponse, +) async def pay( - invoice: str = Query(default=..., description="Lightning invoice to pay"), + bolt11: str = Query(default=..., description="Lightning invoice to pay"), mint: str = Query( default=None, description="Mint URL to pay from (None for default mint)", ), -): - if not settings.lightning: - raise Exception("lightning not enabled.") - +) -> PaymentResponse: global wallet - wallet = await mint_wallet(mint) - await wallet.load_proofs(reload=True) + if mint: + wallet = await mint_wallet(mint) + payment_response = await wallet.pay_invoice(bolt11) + return payment_response - total_amount, fee_reserve_sat = await wallet.get_pay_amount_with_fees(invoice) - assert total_amount > 0, "amount has to be larger than zero." - assert wallet.available_balance >= total_amount, "balance is too low." - _, send_proofs = await wallet.split_to_send(wallet.proofs, total_amount) - await wallet.pay_lightning(send_proofs, invoice, fee_reserve_sat) - return PayResponse( - amount=total_amount - fee_reserve_sat, - fee=fee_reserve_sat, - amount_with_fee=total_amount, - ) + +@router.get( + "/lightning/payment_state", + name="Request lightning invoice", + response_model=PaymentStatus, +) +async def payment_state( + payment_hash: str = Query(default=None, description="Id of paid invoice"), + mint: str = Query( + default=None, + description="Mint URL to create an invoice at (None for default mint)", + ), +) -> PaymentStatus: + global wallet + if mint: + wallet = await mint_wallet(mint) + state = await wallet.get_payment_status(payment_hash) + return state @router.post( - "/invoice", name="Request lightning invoice", response_model=InvoiceResponse + "/lightning/create_invoice", + name="Request lightning invoice", + response_model=InvoiceResponse, ) -async def invoice( +async def create_invoice( amount: int = Query(default=..., description="Amount to request in invoice"), - hash: str = Query(default=None, description="Hash of paid invoice"), mint: str = Query( default=None, description="Mint URL to create an invoice at (None for default mint)", ), - split: int = Query( - default=None, description="Split minted tokens with a specific amount." - ), -): - # in case the user wants a specific split, we create a list of amounts - optional_split = None - if split: - assert amount % split == 0, "split must be divisor or amount" - assert amount >= split, "split must smaller or equal amount" - n_splits = amount // split - optional_split = [split] * n_splits - print(f"Requesting split with {n_splits}*{split} sat tokens.") +) -> InvoiceResponse: + global wallet + if mint: + wallet = await mint_wallet(mint) + invoice = await wallet.create_invoice(amount) + return invoice + +@router.get( + "/lightning/invoice_state", + name="Request lightning invoice", + response_model=PaymentStatus, +) +async def invoice_state( + payment_hash: str = Query(default=None, description="Payment hash of paid invoice"), + mint: str = Query( + default=None, + description="Mint URL to create an invoice at (None for default mint)", + ), +) -> PaymentStatus: global wallet - wallet = await mint_wallet(mint) - if not settings.lightning: - await wallet.mint(amount, split=optional_split) - return InvoiceResponse( - amount=amount, - ) - elif amount and not hash: - invoice = await wallet.request_mint(amount) - return InvoiceResponse( - amount=amount, - invoice=invoice, - ) - elif amount and hash: - await wallet.mint(amount, split=optional_split, hash=hash) - return InvoiceResponse( - amount=amount, - hash=hash, - ) - return + if mint: + wallet = await mint_wallet(mint) + state = await wallet.get_invoice_status(payment_hash) + return state + + +@router.get( + "/lightning/balance", + name="Balance", + summary="Display balance.", + response_model=StatusResponse, +) +async def lightning_balance() -> StatusResponse: + try: + await wallet.load_proofs(reload=True) + except Exception as exc: + return StatusResponse(error_message=str(exc), balance_msat=0) + return StatusResponse( + error_message=None, balance_msat=wallet.available_balance * 1000 + ) @router.post( @@ -171,7 +192,7 @@ async def swap( # pay invoice from outgoing mint await outgoing_wallet.load_proofs(reload=True) total_amount, fee_reserve_sat = await outgoing_wallet.get_pay_amount_with_fees( - invoice.pr + invoice.bolt11 ) assert total_amount > 0, "amount must be positive" if outgoing_wallet.available_balance < total_amount: @@ -180,10 +201,10 @@ async def swap( _, send_proofs = await outgoing_wallet.split_to_send( outgoing_wallet.proofs, total_amount, set_reserved=True ) - await outgoing_wallet.pay_lightning(send_proofs, invoice.pr, fee_reserve_sat) + await outgoing_wallet.pay_lightning(send_proofs, invoice.bolt11, fee_reserve_sat) # mint token in incoming mint - await incoming_wallet.mint(amount, hash=invoice.hash) + await incoming_wallet.mint(amount, id=invoice.id) await incoming_wallet.load_proofs(reload=True) mint_balances = await incoming_wallet.balance_per_minturl() return SwapResponse( @@ -223,6 +244,8 @@ async def send_command( ), ): global wallet + if mint: + wallet = await mint_wallet(mint) if not nostr: balance, token = await send( wallet, amount=amount, lock=lock, legacy=False, split=not nosplit @@ -284,7 +307,7 @@ async def burn( if not (all or token or force or delete) or (token and all): raise Exception( "enter a token or use --all to burn all pending tokens, --force to" - " check all tokensor --delete with send ID to force-delete pending" + " check all tokens or --delete with send ID to force-delete pending" " token from list if mint is unavailable.", ) if all: diff --git a/cashu/wallet/cli/cli.py b/cashu/wallet/cli/cli.py index 39174dd0..ca2b52e3 100644 --- a/cashu/wallet/cli/cli.py +++ b/cashu/wallet/cli/cli.py @@ -168,9 +168,12 @@ async def pay(ctx: Context, invoice: str, yes: bool): wallet.status() total_amount, fee_reserve_sat = await wallet.get_pay_amount_with_fees(invoice) if not yes: + potential = ( + f" ({total_amount} sat with potential fees)" if fee_reserve_sat else "" + ) + message = f"Pay {total_amount - fee_reserve_sat} sat{potential}?" click.confirm( - f"Pay {total_amount - fee_reserve_sat} sat ({total_amount} sat with" - " potential fees)?", + message, abort=True, default=True, ) @@ -187,7 +190,7 @@ async def pay(ctx: Context, invoice: str, yes: bool): @cli.command("invoice", help="Create Lighting invoice.") @click.argument("amount", type=int) -@click.option("--hash", default="", help="Hash of the paid invoice.", type=str) +@click.option("--id", default="", help="Id of the paid invoice.", type=str) @click.option( "--split", "-s", @@ -197,7 +200,7 @@ async def pay(ctx: Context, invoice: str, yes: bool): ) @click.pass_context @coro -async def invoice(ctx: Context, amount: int, hash: str, split: int): +async def invoice(ctx: Context, amount: int, id: str, split: int): wallet: Wallet = ctx.obj["WALLET"] await wallet.load_mint() wallet.status() @@ -213,16 +216,16 @@ async def invoice(ctx: Context, amount: int, hash: str, split: int): if not settings.lightning: await wallet.mint(amount, split=optional_split) # user requests an invoice - elif amount and not hash: + elif amount and not id: invoice = await wallet.request_mint(amount) - if invoice.pr: + if invoice.bolt11: print(f"Pay invoice to mint {amount} sat:") print("") - print(f"Invoice: {invoice.pr}") + print(f"Invoice: {invoice.bolt11}") print("") print( "If you abort this you can use this command to recheck the" - f" invoice:\ncashu invoice {amount} --hash {invoice.hash}" + f" invoice:\ncashu invoice {amount} --id {invoice.id}" ) check_until = time.time() + 5 * 60 # check for five minutes print("") @@ -235,7 +238,7 @@ async def invoice(ctx: Context, amount: int, hash: str, split: int): while time.time() < check_until and not paid: time.sleep(3) try: - await wallet.mint(amount, split=optional_split, hash=invoice.hash) + await wallet.mint(amount, split=optional_split, id=invoice.id) paid = True print(" Invoice paid.") except Exception as e: @@ -253,8 +256,8 @@ async def invoice(ctx: Context, amount: int, hash: str, split: int): ) # user paid invoice and want to check it - elif amount and hash: - await wallet.mint(amount, split=optional_split, hash=hash) + elif amount and id: + await wallet.mint(amount, split=optional_split, id=id) wallet.status() return @@ -285,17 +288,17 @@ async def swap(ctx: Context): # pay invoice from outgoing mint total_amount, fee_reserve_sat = await outgoing_wallet.get_pay_amount_with_fees( - invoice.pr + invoice.bolt11 ) if outgoing_wallet.available_balance < total_amount: raise Exception("balance too low") _, send_proofs = await outgoing_wallet.split_to_send( outgoing_wallet.proofs, total_amount, set_reserved=True ) - await outgoing_wallet.pay_lightning(send_proofs, invoice.pr, fee_reserve_sat) + await outgoing_wallet.pay_lightning(send_proofs, invoice.bolt11, fee_reserve_sat) # mint token in incoming mint - await incoming_wallet.mint(amount, hash=invoice.hash) + await incoming_wallet.mint(amount, id=invoice.id) await incoming_wallet.load_proofs(reload=True) await print_mint_balances(incoming_wallet, show_mints=True) @@ -629,8 +632,8 @@ async def invoices(ctx): print(f"Paid: {invoice.paid}") print(f"Incoming: {invoice.amount > 0}") print(f"Amount: {abs(invoice.amount)}") - if invoice.hash: - print(f"Hash: {invoice.hash}") + if invoice.id: + print(f"ID: {invoice.id}") if invoice.preimage: print(f"Preimage: {invoice.preimage}") if invoice.time_created: @@ -644,7 +647,7 @@ async def invoices(ctx): ) print(f"Paid: {d}") print("") - print(f"Payment request: {invoice.pr}") + print(f"Payment request: {invoice.bolt11}") print("") print("--------------------------\n") else: diff --git a/cashu/wallet/crud.py b/cashu/wallet/crud.py index 9101aa48..844bf63e 100644 --- a/cashu/wallet/crud.py +++ b/cashu/wallet/crud.py @@ -14,8 +14,8 @@ async def store_proof( await (conn or db).execute( """ INSERT INTO proofs - (id, amount, C, secret, time_created, derivation_path, dleq) - VALUES (?, ?, ?, ?, ?, ?, ?) + (id, amount, C, secret, time_created, derivation_path, dleq, mint_id, melt_id) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( proof.id, @@ -25,18 +25,42 @@ async def store_proof( int(time.time()), proof.derivation_path, json.dumps(proof.dleq.dict()) if proof.dleq else "", + proof.mint_id, + proof.melt_id, ), ) async def get_proofs( + *, db: Database, + melt_id: str = "", + mint_id: str = "", + table: str = "proofs", conn: Optional[Connection] = None, -) -> List[Proof]: - rows = await (conn or db).fetchall(""" - SELECT * from proofs - """) - return [Proof.from_dict(dict(r)) for r in rows] +): + clauses = [] + values: List[Any] = [] + + if melt_id: + clauses.append("melt_id = ?") + values.append(melt_id) + if mint_id: + clauses.append("mint_id = ?") + values.append(mint_id) + where = "" + if clauses: + where = f"WHERE {' AND '.join(clauses)}" + rows = ( + await (conn or db).fetchall( + f""" + SELECT * from {table} + {where} + """, + tuple(values), + ), + ) + return [Proof.from_dict(dict(r)) for r in rows[0]] if rows else [] async def get_reserved_proofs( @@ -66,8 +90,8 @@ async def invalidate_proof( await (conn or db).execute( """ INSERT INTO proofs_used - (amount, C, secret, time_used, id, derivation_path) - VALUES (?, ?, ?, ?, ?, ?) + (amount, C, secret, time_used, id, derivation_path, mint_id, melt_id) + VALUES (?, ?, ?, ?, ?, ?, ?, ?) """, ( proof.amount, @@ -76,14 +100,19 @@ async def invalidate_proof( int(time.time()), proof.id, proof.derivation_path, + proof.mint_id, + proof.melt_id, ), ) -async def update_proof_reserved( +async def update_proof( proof: Proof, - reserved: bool, - send_id: str = "", + *, + reserved: Optional[bool] = None, + send_id: Optional[str] = None, + mint_id: Optional[str] = None, + melt_id: Optional[str] = None, db: Optional[Database] = None, conn: Optional[Connection] = None, ) -> None: @@ -92,15 +121,22 @@ async def update_proof_reserved( clauses.append("reserved = ?") values.append(reserved) - if send_id: + if send_id is not None: clauses.append("send_id = ?") values.append(send_id) - if reserved: - # set the time of reserving + if reserved is not None: clauses.append("time_reserved = ?") values.append(int(time.time())) + if mint_id is not None: + clauses.append("mint_id = ?") + values.append(mint_id) + + if melt_id is not None: + clauses.append("melt_id = ?") + values.append(melt_id) + await (conn or db).execute( # type: ignore f"UPDATE proofs SET {', '.join(clauses)} WHERE secret = ?", (*values, str(proof.secret)), @@ -184,44 +220,55 @@ async def store_lightning_invoice( await (conn or db).execute( """ INSERT INTO invoices - (amount, pr, hash, preimage, paid, time_created, time_paid) - VALUES (?, ?, ?, ?, ?, ?, ?) + (amount, bolt11, id, payment_hash, preimage, paid, time_created, time_paid, out) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( invoice.amount, - invoice.pr, - invoice.hash, + invoice.bolt11, + invoice.id, + invoice.payment_hash, invoice.preimage, invoice.paid, invoice.time_created, invoice.time_paid, + invoice.out, ), ) async def get_lightning_invoice( + *, db: Database, - hash: str = "", + id: str = "", + payment_hash: str = "", + out: Optional[bool] = None, conn: Optional[Connection] = None, -) -> Invoice: +) -> Optional[Invoice]: clauses = [] values: List[Any] = [] - if hash: - clauses.append("hash = ?") - values.append(hash) + if id: + clauses.append("id = ?") + values.append(id) + if payment_hash: + clauses.append("payment_hash = ?") + values.append(payment_hash) + if out is not None: + clauses.append("out = ?") + values.append(out) where = "" if clauses: where = f"WHERE {' AND '.join(clauses)}" - - row = await (conn or db).fetchone( - f""" + query = f""" SELECT * from invoices {where} - """, + """ + row = await (conn or db).fetchone( + query, tuple(values), ) - return Invoice(**row) + return Invoice(**row) if row else None async def get_lightning_invoices( @@ -252,9 +299,10 @@ async def get_lightning_invoices( async def update_lightning_invoice( db: Database, - hash: str, + id: str, paid: bool, time_paid: Optional[int] = None, + preimage: Optional[str] = None, conn: Optional[Connection] = None, ) -> None: clauses = [] @@ -265,12 +313,15 @@ async def update_lightning_invoice( if time_paid: clauses.append("time_paid = ?") values.append(time_paid) + if preimage: + clauses.append("preimage = ?") + values.append(preimage) await (conn or db).execute( - f"UPDATE invoices SET {', '.join(clauses)} WHERE hash = ?", + f"UPDATE invoices SET {', '.join(clauses)} WHERE id = ?", ( *values, - hash, + id, ), ) diff --git a/cashu/wallet/htlc.py b/cashu/wallet/htlc.py index a9e6e87c..8f25fc55 100644 --- a/cashu/wallet/htlc.py +++ b/cashu/wallet/htlc.py @@ -2,7 +2,6 @@ from datetime import datetime, timedelta from typing import List, Optional -from ..core import bolt11 as bolt11 from ..core.base import HTLCWitness, Proof from ..core.db import Database from ..core.htlc import ( diff --git a/cashu/wallet/lightning/__init__.py b/cashu/wallet/lightning/__init__.py new file mode 100644 index 00000000..f47fbd4a --- /dev/null +++ b/cashu/wallet/lightning/__init__.py @@ -0,0 +1 @@ +from .lightning import LightningWallet # noqa diff --git a/cashu/wallet/lightning/data/lightning.db/wallet.sqlite3 b/cashu/wallet/lightning/data/lightning.db/wallet.sqlite3 new file mode 100644 index 00000000..4d8da726 Binary files /dev/null and b/cashu/wallet/lightning/data/lightning.db/wallet.sqlite3 differ diff --git a/cashu/wallet/lightning/lightning.py b/cashu/wallet/lightning/lightning.py new file mode 100644 index 00000000..36b9c8f2 --- /dev/null +++ b/cashu/wallet/lightning/lightning.py @@ -0,0 +1,153 @@ +import bolt11 + +from ...core.helpers import sum_promises +from ...core.settings import settings +from ...lightning.base import ( + InvoiceResponse, + PaymentResponse, + PaymentStatus, + StatusResponse, +) +from ...wallet.crud import get_lightning_invoice, get_proofs +from ..wallet import Wallet + + +class LightningWallet(Wallet): + """ + Lightning wallet interface for Cashu + """ + + async def async_init(self, raise_connection_error: bool = True): + """Async init for lightning wallet""" + settings.tor = False + await self.load_proofs() + try: + await self.load_mint() + except Exception as e: + if raise_connection_error: + raise e + + def __init__(self, *args, **kwargs): + if not args and not kwargs: + pass + super().__init__(*args, **kwargs) + + async def create_invoice(self, amount: int) -> InvoiceResponse: + """Create lightning invoice + + Args: + amount (int): amount in satoshis + Returns: + str: invoice + """ + invoice = await self.request_mint(amount) + return InvoiceResponse( + ok=True, payment_request=invoice.bolt11, checking_id=invoice.payment_hash + ) + + async def pay_invoice(self, pr: str) -> PaymentResponse: + """Pay lightning invoice + + Args: + pr (str): bolt11 payment request + + Returns: + bool: True if successful + """ + total_amount, fee_reserve_sat = await self.get_pay_amount_with_fees(pr) + assert total_amount > 0, "amount is not positive" + if self.available_balance < total_amount: + print("Error: Balance too low.") + return PaymentResponse(ok=False) + _, send_proofs = await self.split_to_send(self.proofs, total_amount) + try: + resp = await self.pay_lightning(send_proofs, pr, fee_reserve_sat) + if resp.change: + fees_paid_sat = fee_reserve_sat - sum_promises(resp.change) + else: + fees_paid_sat = fee_reserve_sat + + invoice_obj = bolt11.decode(pr) + return PaymentResponse( + ok=True, + checking_id=invoice_obj.payment_hash, + preimage=resp.preimage, + fee_msat=fees_paid_sat * 1000, + ) + except Exception as e: + print("Exception:", e) + return PaymentResponse(ok=False, error_message=str(e)) + + async def get_invoice_status(self, payment_hash: str) -> PaymentStatus: + """Get lightning invoice status (incoming) + + Args: + invoice (str): lightning invoice + + Returns: + str: status + """ + invoice = await get_lightning_invoice( + db=self.db, payment_hash=payment_hash, out=False + ) + if not invoice: + return PaymentStatus(paid=None) + if invoice.paid: + return PaymentStatus(paid=True) + try: + # to check the invoice state, we try minting tokens + await self.mint(invoice.amount, id=invoice.id) + return PaymentStatus(paid=True) + except Exception as e: + print(e) + return PaymentStatus(paid=False) + + async def get_payment_status(self, payment_hash: str) -> PaymentStatus: + """Get lightning payment status (outgoing) + + Args: + payment_hash (str): lightning invoice payment_hash + + Returns: + str: status + """ + + # NOTE: consider adding this in wallet.py and update invoice state to paid in DB + + invoice = await get_lightning_invoice( + db=self.db, payment_hash=payment_hash, out=True + ) + + if not invoice: + return PaymentStatus(paid=False) # "invoice not found (in db)" + if invoice.paid: + return PaymentStatus(paid=True, preimage=invoice.preimage) # "paid (in db)" + proofs = await get_proofs(db=self.db, melt_id=invoice.id) + if not proofs: + return PaymentStatus(paid=False) # "proofs not fount (in db)" + proofs_states = await self.check_proof_state(proofs) + if ( + not proofs_states + or not proofs_states.spendable + or not proofs_states.pending + ): + return PaymentStatus(paid=False) # "states not fount" + + if all(proofs_states.spendable) and all(proofs_states.pending): + return PaymentStatus(paid=None) # "pending (with check)" + if not any(proofs_states.spendable) and not any(proofs_states.pending): + # NOTE: consider adding this check in wallet.py and mark the invoice as paid if all proofs are spent + return PaymentStatus(paid=True) # "paid (with check)" + if all(proofs_states.spendable) and not any(proofs_states.pending): + return PaymentStatus(paid=False) # "failed (with check)" + return PaymentStatus(paid=None) # "undefined state" + + async def get_balance(self) -> StatusResponse: + """Get lightning balance + + Returns: + int: balance in satoshis + """ + return StatusResponse( + error_message=None, balance_msat=self.available_balance * 1000 + ) diff --git a/cashu/wallet/migrations.py b/cashu/wallet/migrations.py index a9e86d33..b22d2f6a 100644 --- a/cashu/wallet/migrations.py +++ b/cashu/wallet/migrations.py @@ -1,8 +1,8 @@ -from ..core.db import Database +from ..core.db import Connection, Database -async def m000_create_migrations_table(db: Database): - await db.execute(""" +async def m000_create_migrations_table(conn: Connection): + await conn.execute(""" CREATE TABLE IF NOT EXISTS dbversions ( db TEXT PRIMARY KEY, version INT NOT NULL @@ -11,53 +11,54 @@ async def m000_create_migrations_table(db: Database): async def m001_initial(db: Database): - await db.execute(f""" - CREATE TABLE IF NOT EXISTS proofs ( - amount {db.big_int} NOT NULL, - C TEXT NOT NULL, - secret TEXT NOT NULL, - - UNIQUE (secret) - + async with db.connect() as conn: + await conn.execute(f""" + CREATE TABLE IF NOT EXISTS proofs ( + amount {db.big_int} NOT NULL, + C TEXT NOT NULL, + secret TEXT NOT NULL, + + UNIQUE (secret) + + ); + """) + + await conn.execute(f""" + CREATE TABLE IF NOT EXISTS proofs_used ( + amount {db.big_int} NOT NULL, + C TEXT NOT NULL, + secret TEXT NOT NULL, + + UNIQUE (secret) + + ); + """) + + await conn.execute(""" + CREATE VIEW IF NOT EXISTS balance AS + SELECT COALESCE(SUM(s), 0) AS balance FROM ( + SELECT SUM(amount) AS s + FROM proofs + WHERE amount > 0 ); """) - await db.execute(f""" - CREATE TABLE IF NOT EXISTS proofs_used ( - amount {db.big_int} NOT NULL, - C TEXT NOT NULL, - secret TEXT NOT NULL, - - UNIQUE (secret) - + await conn.execute(""" + CREATE VIEW IF NOT EXISTS balance_used AS + SELECT COALESCE(SUM(s), 0) AS used FROM ( + SELECT SUM(amount) AS s + FROM proofs_used + WHERE amount > 0 ); """) - await db.execute(""" - CREATE VIEW IF NOT EXISTS balance AS - SELECT COALESCE(SUM(s), 0) AS balance FROM ( - SELECT SUM(amount) AS s - FROM proofs - WHERE amount > 0 - ); - """) - - await db.execute(""" - CREATE VIEW IF NOT EXISTS balance_used AS - SELECT COALESCE(SUM(s), 0) AS used FROM ( - SELECT SUM(amount) AS s - FROM proofs_used - WHERE amount > 0 - ); - """) - async def m002_add_proofs_reserved(db: Database): """ Column for marking proofs as reserved when they are being sent. """ - - await db.execute("ALTER TABLE proofs ADD COLUMN reserved BOOL") + async with db.connect() as conn: + await conn.execute("ALTER TABLE proofs ADD COLUMN reserved BOOL") async def m003_add_proofs_sendid_and_timestamps(db: Database): @@ -65,17 +66,19 @@ async def m003_add_proofs_sendid_and_timestamps(db: Database): Column with unique ID for each initiated send attempt so proofs can be later grouped together for each send attempt. """ - await db.execute("ALTER TABLE proofs ADD COLUMN send_id TEXT") - await db.execute("ALTER TABLE proofs ADD COLUMN time_created TIMESTAMP") - await db.execute("ALTER TABLE proofs ADD COLUMN time_reserved TIMESTAMP") - await db.execute("ALTER TABLE proofs_used ADD COLUMN time_used TIMESTAMP") + async with db.connect() as conn: + await conn.execute("ALTER TABLE proofs ADD COLUMN send_id TEXT") + await conn.execute("ALTER TABLE proofs ADD COLUMN time_created TIMESTAMP") + await conn.execute("ALTER TABLE proofs ADD COLUMN time_reserved TIMESTAMP") + await conn.execute("ALTER TABLE proofs_used ADD COLUMN time_used TIMESTAMP") async def m004_p2sh_locks(db: Database): """ DEPRECATED: Stores P2SH addresses and unlock scripts. """ - # await db.execute(""" + # async with db.connect() as conn: + # await conn.execute(""" # CREATE TABLE IF NOT EXISTS p2sh ( # address TEXT NOT NULL, # script TEXT NOT NULL, @@ -92,91 +95,117 @@ async def m005_wallet_keysets(db: Database): """ Stores mint keysets from different mints and epochs. """ - await db.execute(f""" - CREATE TABLE IF NOT EXISTS keysets ( - id TEXT, - mint_url TEXT, - valid_from TIMESTAMP DEFAULT {db.timestamp_now}, - valid_to TIMESTAMP DEFAULT {db.timestamp_now}, - first_seen TIMESTAMP DEFAULT {db.timestamp_now}, - active BOOL DEFAULT TRUE, + async with db.connect() as conn: + await conn.execute(f""" + CREATE TABLE IF NOT EXISTS keysets ( + id TEXT, + mint_url TEXT, + valid_from TIMESTAMP DEFAULT {db.timestamp_now}, + valid_to TIMESTAMP DEFAULT {db.timestamp_now}, + first_seen TIMESTAMP DEFAULT {db.timestamp_now}, + active BOOL DEFAULT TRUE, - UNIQUE (id, mint_url) + UNIQUE (id, mint_url) - ); - """) + ); + """) - await db.execute("ALTER TABLE proofs ADD COLUMN id TEXT") - await db.execute("ALTER TABLE proofs_used ADD COLUMN id TEXT") + await conn.execute("ALTER TABLE proofs ADD COLUMN id TEXT") + await conn.execute("ALTER TABLE proofs_used ADD COLUMN id TEXT") async def m006_invoices(db: Database): """ Stores Lightning invoices. """ - await db.execute(f""" - CREATE TABLE IF NOT EXISTS invoices ( - amount INTEGER NOT NULL, - pr TEXT NOT NULL, - hash TEXT, - preimage TEXT, - paid BOOL DEFAULT FALSE, - time_created TIMESTAMP DEFAULT {db.timestamp_now}, - time_paid TIMESTAMP DEFAULT {db.timestamp_now}, + async with db.connect() as conn: + await conn.execute(f""" + CREATE TABLE IF NOT EXISTS invoices ( + amount INTEGER NOT NULL, + pr TEXT NOT NULL, + hash TEXT, + preimage TEXT, + paid BOOL DEFAULT FALSE, + time_created TIMESTAMP DEFAULT {db.timestamp_now}, + time_paid TIMESTAMP DEFAULT {db.timestamp_now}, - UNIQUE (hash) + UNIQUE (hash) - ); - """) + ); + """) async def m007_nostr(db: Database): """ Stores timestamps of nostr operations. """ - await db.execute(""" - CREATE TABLE IF NOT EXISTS nostr ( - type TEXT NOT NULL, - last TIMESTAMP DEFAULT NULL - ) - """) - await db.execute( - """ - INSERT INTO nostr - (type, last) - VALUES (?, ?) - """, - ( - "dm", - None, - ), - ) + # async with db.connect() as conn: + # await conn.execute(""" + # CREATE TABLE IF NOT EXISTS nostr ( + # type TEXT NOT NULL, + # last TIMESTAMP DEFAULT NULL + # ) + # """) + # await conn.execute( + # """ + # INSERT INTO nostr + # (type, last) + # VALUES (?, ?) + # """, + # ( + # "dm", + # None, + # ), + # ) async def m008_keysets_add_public_keys(db: Database): """ Stores public keys of mint in a new column of table keysets. """ - await db.execute("ALTER TABLE keysets ADD COLUMN public_keys TEXT") + async with db.connect() as conn: + await conn.execute("ALTER TABLE keysets ADD COLUMN public_keys TEXT") async def m009_privatekey_and_determinstic_key_derivation(db: Database): - await db.execute("ALTER TABLE keysets ADD COLUMN counter INTEGER DEFAULT 0") - await db.execute("ALTER TABLE proofs ADD COLUMN derivation_path TEXT") - await db.execute("ALTER TABLE proofs_used ADD COLUMN derivation_path TEXT") - await db.execute(""" - CREATE TABLE IF NOT EXISTS seed ( - seed TEXT NOT NULL, - mnemonic TEXT NOT NULL, - - UNIQUE (seed, mnemonic) - ); - """) - # await db.execute("INSERT INTO secret_derivation (counter) VALUES (0)") + async with db.connect() as conn: + await conn.execute("ALTER TABLE keysets ADD COLUMN counter INTEGER DEFAULT 0") + await conn.execute("ALTER TABLE proofs ADD COLUMN derivation_path TEXT") + await conn.execute("ALTER TABLE proofs_used ADD COLUMN derivation_path TEXT") + await conn.execute(""" + CREATE TABLE IF NOT EXISTS seed ( + seed TEXT NOT NULL, + mnemonic TEXT NOT NULL, + + UNIQUE (seed, mnemonic) + ); + """) + # await conn.execute("INSERT INTO secret_derivation (counter) VALUES (0)") async def m010_add_proofs_dleq(db: Database): """ Columns to store DLEQ proofs for proofs. """ - await db.execute("ALTER TABLE proofs ADD COLUMN dleq TEXT") + async with db.connect() as conn: + await conn.execute("ALTER TABLE proofs ADD COLUMN dleq TEXT") + + +async def m010_add_ids_to_proofs_and_out_to_invoices(db: Database): + """ + Columns that store mint and melt id for proofs and invoices. + """ + async with db.connect() as conn: + await conn.execute("ALTER TABLE proofs ADD COLUMN mint_id TEXT") + await conn.execute("ALTER TABLE proofs_used ADD COLUMN mint_id TEXT") + await conn.execute("ALTER TABLE proofs ADD COLUMN melt_id TEXT") + await conn.execute("ALTER TABLE proofs_used ADD COLUMN melt_id TEXT") + + # column in invoices for marking whether the invoice is incoming (out=False) or outgoing (out=True) + await conn.execute("ALTER TABLE invoices ADD COLUMN out BOOL") + # rename column pr to bolt11 + await conn.execute("ALTER TABLE invoices RENAME COLUMN pr TO bolt11") + # rename column hash to payment_hash + await conn.execute("ALTER TABLE invoices RENAME COLUMN hash TO id") + # add column payment_hash + await conn.execute("ALTER TABLE invoices ADD COLUMN payment_hash TEXT") diff --git a/cashu/wallet/nostr.py b/cashu/wallet/nostr.py index 83dfe21d..42485820 100644 --- a/cashu/wallet/nostr.py +++ b/cashu/wallet/nostr.py @@ -2,8 +2,8 @@ import threading import click +from httpx import ConnectError from loguru import logger -from requests.exceptions import ConnectionError from ..core.settings import settings from ..nostr.client.client import NostrClient @@ -27,11 +27,11 @@ async def nip5_to_pubkey(wallet: Wallet, address: str): user, host = address.split("@") resp_dict = {} try: - resp = wallet.s.get( + resp = await wallet.httpx.get( f"https://{host}/.well-known/nostr.json?name={user}", ) resp.raise_for_status() - except ConnectionError: + except ConnectError: raise Exception(f"Could not connect to {host}") except Exception as e: raise e diff --git a/cashu/wallet/p2pk.py b/cashu/wallet/p2pk.py index 2844bc89..ec4121c3 100644 --- a/cashu/wallet/p2pk.py +++ b/cashu/wallet/p2pk.py @@ -3,7 +3,6 @@ from loguru import logger -from ..core import bolt11 as bolt11 from ..core.base import ( BlindedMessage, P2PKWitness, diff --git a/cashu/wallet/secrets.py b/cashu/wallet/secrets.py index 45601758..f22d3ea5 100644 --- a/cashu/wallet/secrets.py +++ b/cashu/wallet/secrets.py @@ -6,7 +6,6 @@ from loguru import logger from mnemonic import Mnemonic -from ..core import bolt11 as bolt11 from ..core.crypto.secp import PrivateKey from ..core.db import Database from ..core.settings import settings diff --git a/cashu/wallet/wallet.py b/cashu/wallet/wallet.py index 3c3f7ba8..ecfc1b03 100644 --- a/cashu/wallet/wallet.py +++ b/cashu/wallet/wallet.py @@ -1,19 +1,18 @@ import base64 import json import math -import secrets as scrts import time import uuid from itertools import groupby from posixpath import join from typing import Dict, List, Optional, Tuple, Union -import requests +import bolt11 +import httpx from bip32 import BIP32 +from httpx import Response from loguru import logger -from requests import Response -from ..core import bolt11 as bolt11 from ..core.base import ( BlindedMessage, BlindedSignature, @@ -38,7 +37,6 @@ TokenV3Token, WalletKeyset, ) -from ..core.bolt11 import Invoice as InvoiceBolt11 from ..core.crypto import b_dhke from ..core.crypto.secp import PrivateKey, PublicKey from ..core.db import Database @@ -59,7 +57,7 @@ store_lightning_invoice, store_proof, update_lightning_invoice, - update_proof_reserved, + update_proof, ) from . import migrations from .htlc import WalletHTLC @@ -67,19 +65,16 @@ from .secrets import WalletSecrets -def async_set_requests(func): +def async_set_httpx_client(func): """ Decorator that wraps around any async class method of LedgerAPI that makes API calls. Sets some HTTP headers and starts a Tor instance if none is - already running and and sets local proxy to use it. + already running and and sets local proxy to use it. """ async def wrapper(self, *args, **kwargs): - self.s.headers.update({"Client-version": settings.version}) - if settings.debug: - self.s.verify = False - # set proxy + proxies_dict = {} proxy_url: Union[str, None] = None if settings.tor and TorProxy().check_platform(): self.tor = TorProxy(timeout=True) @@ -90,10 +85,17 @@ async def wrapper(self, *args, **kwargs): elif settings.http_proxy: proxy_url = settings.http_proxy if proxy_url: - self.s.proxies.update({"http": proxy_url}) - self.s.proxies.update({"https": proxy_url}) + proxies_dict.update({"http": proxy_url}) + proxies_dict.update({"https": proxy_url}) + + headers_dict = {"Client-version": settings.version} - self.s.headers.update({"User-Agent": scrts.token_urlsafe(8)}) + self.httpx = httpx.AsyncClient( + verify=not settings.debug, + proxies=proxies_dict, # type: ignore + headers=headers_dict, + base_url=self.url, + ) return await func(self, *args, **kwargs) return wrapper @@ -106,16 +108,15 @@ class LedgerAPI(object): mint_info: GetInfoResponse # holds info about mint tor: TorProxy - s: requests.Session db: Database + httpx: httpx.AsyncClient def __init__(self, url: str, db: Database): self.url = url - self.s = requests.Session() self.db = db self.keysets = {} - @async_set_requests + @async_set_httpx_client async def _init_s(self): """Dummy function that can be called from outside to use LedgerAPI.s""" return @@ -262,7 +263,7 @@ async def _check_used_secrets(self, secrets): ENDPOINTS """ - @async_set_requests + @async_set_httpx_client async def _get_keys(self, url: str) -> WalletKeyset: """API that gets the current keys of the mint @@ -275,7 +276,7 @@ async def _get_keys(self, url: str) -> WalletKeyset: Raises: Exception: If no keys are received from the mint """ - resp = self.s.get( + resp = await self.httpx.get( join(url, "keys"), ) self.raise_on_error(resp) @@ -288,7 +289,7 @@ async def _get_keys(self, url: str) -> WalletKeyset: keyset = WalletKeyset(public_keys=keyset_keys, mint_url=url) return keyset - @async_set_requests + @async_set_httpx_client async def _get_keys_of_keyset(self, url: str, keyset_id: str) -> WalletKeyset: """API that gets the keys of a specific keyset from the mint. @@ -304,7 +305,7 @@ async def _get_keys_of_keyset(self, url: str, keyset_id: str) -> WalletKeyset: Exception: If no keys are received from the mint """ keyset_id_urlsafe = keyset_id.replace("+", "-").replace("/", "_") - resp = self.s.get( + resp = await self.httpx.get( join(url, f"keys/{keyset_id_urlsafe}"), ) self.raise_on_error(resp) @@ -317,7 +318,7 @@ async def _get_keys_of_keyset(self, url: str, keyset_id: str) -> WalletKeyset: keyset = WalletKeyset(id=keyset_id, public_keys=keyset_keys, mint_url=url) return keyset - @async_set_requests + @async_set_httpx_client async def _get_keyset_ids(self, url: str) -> List[str]: """API that gets a list of all active keysets of the mint. @@ -330,7 +331,7 @@ async def _get_keyset_ids(self, url: str) -> List[str]: Raises: Exception: If no keysets are received from the mint """ - resp = self.s.get( + resp = await self.httpx.get( join(url, "keysets"), ) self.raise_on_error(resp) @@ -339,7 +340,7 @@ async def _get_keyset_ids(self, url: str) -> List[str]: assert len(keysets.keysets), Exception("did not receive any keysets") return keysets.keysets - @async_set_requests + @async_set_httpx_client async def _get_info(self, url: str) -> GetInfoResponse: """API that gets the mint info. @@ -352,7 +353,7 @@ async def _get_info(self, url: str) -> GetInfoResponse: Raises: Exception: If the mint info request fails """ - resp = self.s.get( + resp = await self.httpx.get( join(url, "info"), ) self.raise_on_error(resp) @@ -360,7 +361,7 @@ async def _get_info(self, url: str) -> GetInfoResponse: mint_info: GetInfoResponse = GetInfoResponse.parse_obj(data) return mint_info - @async_set_requests + @async_set_httpx_client async def request_mint(self, amount) -> Invoice: """Requests a mint from the server and returns Lightning invoice. @@ -374,21 +375,29 @@ async def request_mint(self, amount) -> Invoice: Exception: If the mint request fails """ logger.trace("Requesting mint: GET /mint") - resp = self.s.get(join(self.url, "mint"), params={"amount": amount}) + resp = await self.httpx.get(join(self.url, "mint"), params={"amount": amount}) self.raise_on_error(resp) return_dict = resp.json() mint_response = GetMintResponse.parse_obj(return_dict) - return Invoice(amount=amount, pr=mint_response.pr, hash=mint_response.hash) + decoded_invoice = bolt11.decode(mint_response.pr) + return Invoice( + amount=amount, + bolt11=mint_response.pr, + payment_hash=decoded_invoice.payment_hash, + id=mint_response.hash, + out=False, + time_created=int(time.time()), + ) - @async_set_requests + @async_set_httpx_client async def mint( - self, outputs: List[BlindedMessage], hash: Optional[str] = None + self, outputs: List[BlindedMessage], id: Optional[str] = None ) -> List[BlindedSignature]: """Mints new coins and returns a proof of promise. Args: outputs (List[BlindedMessage]): Outputs to mint new tokens with - hash (str, optional): Hash of the paid invoice. Defaults to None. + id (str, optional): Id of the paid invoice. Defaults to None. Returns: list[Proof]: List of proofs. @@ -398,12 +407,12 @@ async def mint( """ outputs_payload = PostMintRequest(outputs=outputs) logger.trace("Checking Lightning invoice. POST /mint") - resp = self.s.post( + resp = await self.httpx.post( join(self.url, "mint"), json=outputs_payload.dict(), params={ - "hash": hash, - "payment_hash": hash, # backwards compatibility pre 0.12.0 + "hash": id, + "payment_hash": id, # backwards compatibility pre 0.12.0 }, ) self.raise_on_error(resp) @@ -412,7 +421,7 @@ async def mint( promises = PostMintResponse.parse_obj(response_dict).promises return promises - @async_set_requests + @async_set_httpx_client async def split( self, proofs: List[Proof], @@ -437,7 +446,7 @@ def _splitrequest_include_fields(proofs: List[Proof]): "proofs": {i: proofs_include for i in range(len(proofs))}, } - resp = self.s.post( + resp = await self.httpx.post( join(self.url, "split"), json=split_payload.dict(include=_splitrequest_include_fields(proofs)), # type: ignore ) @@ -451,7 +460,7 @@ def _splitrequest_include_fields(proofs: List[Proof]): return promises - @async_set_requests + @async_set_httpx_client async def check_proof_state(self, proofs: List[Proof]): """ Checks whether the secrets in proofs are already spent or not and returns a list of booleans. @@ -464,7 +473,7 @@ def _check_proof_state_include_fields(proofs): "proofs": {i: {"secret"} for i in range(len(proofs))}, } - resp = self.s.post( + resp = await self.httpx.post( join(self.url, "check"), json=payload.dict(include=_check_proof_state_include_fields(proofs)), # type: ignore ) @@ -474,11 +483,11 @@ def _check_proof_state_include_fields(proofs): states = CheckSpendableResponse.parse_obj(return_dict) return states - @async_set_requests + @async_set_httpx_client async def check_fees(self, payment_request: str): """Checks whether the Lightning payment is internal.""" payload = CheckFeesRequest(pr=payment_request) - resp = self.s.post( + resp = await self.httpx.post( join(self.url, "checkfees"), json=payload.dict(), ) @@ -487,15 +496,16 @@ async def check_fees(self, payment_request: str): return_dict = resp.json() return return_dict - @async_set_requests + @async_set_httpx_client async def pay_lightning( self, proofs: List[Proof], invoice: str, outputs: Optional[List[BlindedMessage]] - ): + ) -> GetMeltResponse: """ Accepts proofs and a lightning invoice to pay in exchange. """ payload = PostMeltRequest(proofs=proofs, pr=invoice, outputs=outputs) + logger.debug("Calling melt. POST /melt") def _meltrequest_include_fields(proofs: List[Proof]): """strips away fields from the model that aren't necessary for the /melt""" @@ -506,16 +516,17 @@ def _meltrequest_include_fields(proofs: List[Proof]): "outputs": ..., } - resp = self.s.post( + resp = await self.httpx.post( join(self.url, "melt"), json=payload.dict(include=_meltrequest_include_fields(proofs)), # type: ignore + timeout=None, ) self.raise_on_error(resp) return_dict = resp.json() return GetMeltResponse.parse_obj(return_dict) - @async_set_requests + @async_set_httpx_client async def restore_promises( self, outputs: List[BlindedMessage] ) -> Tuple[List[BlindedMessage], List[BlindedSignature]]: @@ -523,7 +534,7 @@ async def restore_promises( Asks the mint to restore promises corresponding to outputs. """ payload = PostMintRequest(outputs=outputs) - resp = self.s.post(join(self.url, "restore"), json=payload.dict()) + resp = await self.httpx.post(join(self.url, "restore"), json=payload.dict()) self.raise_on_error(resp) response_dict = resp.json() returnObj = PostRestoreResponse.parse_obj(response_dict) @@ -620,7 +631,6 @@ async def request_mint(self, amount: int) -> Invoice: Invoice: Lightning invoice """ invoice = await super().request_mint(amount) - invoice.time_created = int(time.time()) await store_lightning_invoice(db=self.db, invoice=invoice) return invoice @@ -628,14 +638,14 @@ async def mint( self, amount: int, split: Optional[List[int]] = None, - hash: Optional[str] = None, + id: Optional[str] = None, ) -> List[Proof]: """Mint tokens of a specific amount after an invoice has been paid. Args: amount (int): Total amount of tokens to be minted split (Optional[List[str]], optional): List of desired amount splits to be minted. Total must sum to `amount`. - hash (Optional[str], optional): Hash for looking up the paid Lightning invoice. Defaults to None (for testing with LIGHTNING=False). + id (Optional[str], optional): Id for looking up the paid Lightning invoice. Defaults to None (for testing with LIGHTNING=False). Raises: Exception: Raises exception if `amounts` does not sum to `amount` or has unsupported value. @@ -668,7 +678,7 @@ async def mint( outputs, rs = self._construct_outputs(amounts, secrets, rs) # will raise exception if mint is unsuccessful - promises = await super().mint(outputs, hash) + promises = await super().mint(outputs, id) # success, bump secret counter in database await bump_secret_derivation( @@ -676,10 +686,15 @@ async def mint( ) proofs = await self._construct_proofs(promises, secrets, rs, derivation_paths) - if hash: + if id: await update_lightning_invoice( - db=self.db, hash=hash, paid=True, time_paid=int(time.time()) + db=self.db, id=id, paid=True, time_paid=int(time.time()) ) + # store the mint_id in proofs + async with self.db.connect() as conn: + for p in proofs: + p.mint_id = id + await update_proof(p, mint_id=id, conn=conn) return proofs async def redeem( @@ -782,7 +797,7 @@ async def split( async def pay_lightning( self, proofs: List[Proof], invoice: str, fee_reserve_sat: int - ) -> bool: + ) -> GetMeltResponse: """Pays a lightning invoice and returns the status of the payment. Args: @@ -795,41 +810,72 @@ async def pay_lightning( # Generate a number of blank outputs for any overpaid fees. As described in # NUT-08, the mint will imprint these outputs with a value depending on the # amount of fees we overpaid. - n_return_outputs = calculate_number_of_blank_outputs(fee_reserve_sat) - secrets, rs, derivation_paths = await self.generate_n_secrets(n_return_outputs) - outputs, rs = self._construct_outputs(n_return_outputs * [0], secrets, rs) - - status = await super().pay_lightning(proofs, invoice, outputs) - - if status.paid: - # the payment was successful - invoice_obj = Invoice( - amount=-sum_proofs(proofs), - pr=invoice, - preimage=status.preimage, - paid=True, - time_paid=time.time(), - hash="", - ) - # we have a unique constraint on the hash, so we generate a random one if it doesn't exist - invoice_obj.hash = invoice_obj.hash or await self._generate_secret() - await store_lightning_invoice(db=self.db, invoice=invoice_obj) - - # handle change and produce proofs - if status.change: - change_proofs = await self._construct_proofs( - status.change, - secrets[: len(status.change)], - rs[: len(status.change)], - derivation_paths[: len(status.change)], - ) - logger.debug(f"Received change: {sum_proofs(change_proofs)} sat") + n_change_outputs = calculate_number_of_blank_outputs(fee_reserve_sat) + change_secrets, change_rs, change_derivation_paths = ( + await self.generate_n_secrets(n_change_outputs) + ) + change_outputs, change_rs = self._construct_outputs( + n_change_outputs * [1], change_secrets, change_rs + ) - await self.invalidate(proofs) + # we store the invoice object in the database to later be able to check the invoice state + # generate a random ID for this transaction + melt_id = await self._generate_secret() - else: + # store the melt_id in proofs + async with self.db.connect() as conn: + for p in proofs: + p.melt_id = melt_id + await update_proof(p, melt_id=melt_id, conn=conn) + + decoded_invoice = bolt11.decode(invoice) + invoice_obj = Invoice( + amount=-sum_proofs(proofs), + bolt11=invoice, + payment_hash=decoded_invoice.payment_hash, + # preimage=status.preimage, + paid=False, + time_paid=int(time.time()), + id=melt_id, # store the same ID in the invoice + out=True, # outgoing invoice + ) + # store invoice in db as not paid yet + await store_lightning_invoice(db=self.db, invoice=invoice_obj) + + status = await super().pay_lightning(proofs, invoice, change_outputs) + + # if payment fails + if not status.paid: + # remove the melt_id in proofs + for p in proofs: + p.melt_id = None + await update_proof(p, melt_id=None, db=self.db) raise Exception("could not pay invoice.") - return status.paid + + # invoice was paid successfully + # we don't have to recheck the spendable sate of these tokens when invalidating + await self.invalidate(proofs, check_spendable=False) + + # update paid status in db + logger.trace(f"Settings invoice {melt_id} to paid.") + await update_lightning_invoice( + db=self.db, + id=melt_id, + paid=True, + time_paid=int(time.time()), + preimage=status.preimage, + ) + + # handle change and produce proofs + if status.change: + change_proofs = await self._construct_proofs( + status.change, + change_secrets[: len(status.change)], + change_rs[: len(status.change)], + change_derivation_paths[: len(status.change)], + ) + logger.debug(f"Received change: {sum_proofs(change_proofs)} sat") + return status async def check_proof_state(self, proofs): return await super().check_proof_state(proofs) @@ -965,7 +1011,7 @@ def _construct_outputs( async def _store_proofs(self, proofs): try: - async with self.db.connect() as conn: # type: ignore + async with self.db.connect() as conn: for proof in proofs: await store_proof(proof, db=self.db, conn=conn) except Exception as e: @@ -1171,7 +1217,7 @@ async def _select_proofs_to_send( proof_to_add = sorted_proofs_of_current_keyset.pop() send_proofs.append(proof_to_add) - logger.debug(f"selected proof amounts: {[p.amount for p in send_proofs]}") + logger.trace(f"selected proof amounts: {[p.amount for p in send_proofs]}") return send_proofs async def set_reserved(self, proofs: List[Proof], reserved: bool) -> None: @@ -1184,9 +1230,7 @@ async def set_reserved(self, proofs: List[Proof], reserved: bool) -> None: uuid_str = str(uuid.uuid1()) for proof in proofs: proof.reserved = True - await update_proof_reserved( - proof, reserved=reserved, send_id=uuid_str, db=self.db - ) + await update_proof(proof, reserved=reserved, send_id=uuid_str, db=self.db) async def invalidate( self, proofs: List[Proof], check_spendable=True @@ -1232,7 +1276,8 @@ async def get_pay_amount_with_fees(self, invoice: str): Decodes the amount from a Lightning invoice and returns the total amount (amount+fees) to be paid. """ - decoded_invoice: InvoiceBolt11 = bolt11.decode(invoice) + decoded_invoice = bolt11.decode(invoice) + assert decoded_invoice.amount_msat, "invoices has no amount." # check if it's an internal payment fees = int((await self.check_fees(invoice))["fee"]) logger.debug(f"Mint wants {fees} sat as fee reserve.") diff --git a/mypy.ini b/mypy.ini index deb32fd2..0df56bd0 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1,6 +1,7 @@ [mypy] python_version = 3.9 # disallow_untyped_defs = True +; check_untyped_defs = True ignore_missing_imports = True [mypy-cashu.nostr.*] diff --git a/poetry.lock b/poetry.lock index b9472a23..6b230981 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2,25 +2,25 @@ [[package]] name = "anyio" -version = "3.7.1" +version = "4.0.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "anyio-3.7.1-py3-none-any.whl", hash = "sha256:91dee416e570e92c64041bd18b900d1d6fa78dff7048769ce5ac5ddad004fbb5"}, - {file = "anyio-3.7.1.tar.gz", hash = "sha256:44a3c9aba0f5defa43261a8b3efb97891f2bd7d804e0e1f56419befa1adfc780"}, + {file = "anyio-4.0.0-py3-none-any.whl", hash = "sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f"}, + {file = "anyio-4.0.0.tar.gz", hash = "sha256:f7ed51751b2c2add651e5747c891b47e26d2a21be5d32d9311dfe9692f3e5d7a"}, ] [package.dependencies] -exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} +exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} idna = ">=2.8" sniffio = ">=1.1" [package.extras] -doc = ["Sphinx", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-jquery"] -test = ["anyio[trio]", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] -trio = ["trio (<0.22)"] +doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] +trio = ["trio (>=0.22)"] [[package]] name = "asn1crypto" @@ -111,34 +111,34 @@ files = [ [[package]] name = "black" -version = "23.7.0" +version = "23.9.1" description = "The uncompromising code formatter." category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "black-23.7.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:5c4bc552ab52f6c1c506ccae05681fab58c3f72d59ae6e6639e8885e94fe2587"}, - {file = "black-23.7.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:552513d5cd5694590d7ef6f46e1767a4df9af168d449ff767b13b084c020e63f"}, - {file = "black-23.7.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:86cee259349b4448adb4ef9b204bb4467aae74a386bce85d56ba4f5dc0da27be"}, - {file = "black-23.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:501387a9edcb75d7ae8a4412bb8749900386eaef258f1aefab18adddea1936bc"}, - {file = "black-23.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:fb074d8b213749fa1d077d630db0d5f8cc3b2ae63587ad4116e8a436e9bbe995"}, - {file = "black-23.7.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:b5b0ee6d96b345a8b420100b7d71ebfdd19fab5e8301aff48ec270042cd40ac2"}, - {file = "black-23.7.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:893695a76b140881531062d48476ebe4a48f5d1e9388177e175d76234ca247cd"}, - {file = "black-23.7.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:c333286dc3ddca6fdff74670b911cccedacb4ef0a60b34e491b8a67c833b343a"}, - {file = "black-23.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:831d8f54c3a8c8cf55f64d0422ee875eecac26f5f649fb6c1df65316b67c8926"}, - {file = "black-23.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:7f3bf2dec7d541b4619b8ce526bda74a6b0bffc480a163fed32eb8b3c9aed8ad"}, - {file = "black-23.7.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:f9062af71c59c004cd519e2fb8f5d25d39e46d3af011b41ab43b9c74e27e236f"}, - {file = "black-23.7.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:01ede61aac8c154b55f35301fac3e730baf0c9cf8120f65a9cd61a81cfb4a0c3"}, - {file = "black-23.7.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:327a8c2550ddc573b51e2c352adb88143464bb9d92c10416feb86b0f5aee5ff6"}, - {file = "black-23.7.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1c6022b86f83b632d06f2b02774134def5d4d4f1dac8bef16d90cda18ba28a"}, - {file = "black-23.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:27eb7a0c71604d5de083757fbdb245b1a4fae60e9596514c6ec497eb63f95320"}, - {file = "black-23.7.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:8417dbd2f57b5701492cd46edcecc4f9208dc75529bcf76c514864e48da867d9"}, - {file = "black-23.7.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:47e56d83aad53ca140da0af87678fb38e44fd6bc0af71eebab2d1f59b1acf1d3"}, - {file = "black-23.7.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:25cc308838fe71f7065df53aedd20327969d05671bac95b38fdf37ebe70ac087"}, - {file = "black-23.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:642496b675095d423f9b8448243336f8ec71c9d4d57ec17bf795b67f08132a91"}, - {file = "black-23.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:ad0014efc7acf0bd745792bd0d8857413652979200ab924fbf239062adc12491"}, - {file = "black-23.7.0-py3-none-any.whl", hash = "sha256:9fd59d418c60c0348505f2ddf9609c1e1de8e7493eab96198fc89d9f865e7a96"}, - {file = "black-23.7.0.tar.gz", hash = "sha256:022a582720b0d9480ed82576c920a8c1dde97cc38ff11d8d8859b3bd6ca9eedb"}, + {file = "black-23.9.1-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:d6bc09188020c9ac2555a498949401ab35bb6bf76d4e0f8ee251694664df6301"}, + {file = "black-23.9.1-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:13ef033794029b85dfea8032c9d3b92b42b526f1ff4bf13b2182ce4e917f5100"}, + {file = "black-23.9.1-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:75a2dc41b183d4872d3a500d2b9c9016e67ed95738a3624f4751a0cb4818fe71"}, + {file = "black-23.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13a2e4a93bb8ca74a749b6974925c27219bb3df4d42fc45e948a5d9feb5122b7"}, + {file = "black-23.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:adc3e4442eef57f99b5590b245a328aad19c99552e0bdc7f0b04db6656debd80"}, + {file = "black-23.9.1-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:8431445bf62d2a914b541da7ab3e2b4f3bc052d2ccbf157ebad18ea126efb91f"}, + {file = "black-23.9.1-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:8fc1ddcf83f996247505db6b715294eba56ea9372e107fd54963c7553f2b6dfe"}, + {file = "black-23.9.1-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:7d30ec46de88091e4316b17ae58bbbfc12b2de05e069030f6b747dfc649ad186"}, + {file = "black-23.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:031e8c69f3d3b09e1aa471a926a1eeb0b9071f80b17689a655f7885ac9325a6f"}, + {file = "black-23.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:538efb451cd50f43aba394e9ec7ad55a37598faae3348d723b59ea8e91616300"}, + {file = "black-23.9.1-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:638619a559280de0c2aa4d76f504891c9860bb8fa214267358f0a20f27c12948"}, + {file = "black-23.9.1-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:a732b82747235e0542c03bf352c126052c0fbc458d8a239a94701175b17d4855"}, + {file = "black-23.9.1-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:cf3a4d00e4cdb6734b64bf23cd4341421e8953615cba6b3670453737a72ec204"}, + {file = "black-23.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf99f3de8b3273a8317681d8194ea222f10e0133a24a7548c73ce44ea1679377"}, + {file = "black-23.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:14f04c990259576acd093871e7e9b14918eb28f1866f91968ff5524293f9c573"}, + {file = "black-23.9.1-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:c619f063c2d68f19b2d7270f4cf3192cb81c9ec5bc5ba02df91471d0b88c4c5c"}, + {file = "black-23.9.1-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:6a3b50e4b93f43b34a9d3ef00d9b6728b4a722c997c99ab09102fd5efdb88325"}, + {file = "black-23.9.1-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:c46767e8df1b7beefb0899c4a95fb43058fa8500b6db144f4ff3ca38eb2f6393"}, + {file = "black-23.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50254ebfa56aa46a9fdd5d651f9637485068a1adf42270148cd101cdf56e0ad9"}, + {file = "black-23.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:403397c033adbc45c2bd41747da1f7fc7eaa44efbee256b53842470d4ac5a70f"}, + {file = "black-23.9.1-py3-none-any.whl", hash = "sha256:6ccd59584cc834b6d127628713e4b6b968e5f79572da66284532525a042549f9"}, + {file = "black-23.9.1.tar.gz", hash = "sha256:24b6b3ff5c6d9ea08a8888f6977eae858e1f340d7260cf56d70a49823236b62d"}, ] [package.dependencies] @@ -148,7 +148,7 @@ packaging = ">=22.0" pathspec = ">=0.9.0" platformdirs = ">=2" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} +typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} [package.extras] colorama = ["colorama (>=0.4.3)"] @@ -156,6 +156,26 @@ d = ["aiohttp (>=3.7.4)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] +[[package]] +name = "bolt11" +version = "2.0.5" +description = "A library for encoding and decoding BOLT11 payment requests." +category = "main" +optional = false +python-versions = ">=3.8.1" +files = [ + {file = "bolt11-2.0.5-py3-none-any.whl", hash = "sha256:6791c2edee804a4a8a7d092c689f8d2c01212271a33963ede4a988b7a6ce1b81"}, + {file = "bolt11-2.0.5.tar.gz", hash = "sha256:e6be2748b0c4a017900761f63d9944c1dde8f22fd2829006679a0e2346eaa47b"}, +] + +[package.dependencies] +base58 = "*" +bech32 = "*" +bitstring = "*" +click = "*" +ecdsa = "*" +secp256k1 = "*" + [[package]] name = "certifi" version = "2023.7.22" @@ -170,76 +190,64 @@ files = [ [[package]] name = "cffi" -version = "1.15.1" +version = "1.16.0" description = "Foreign Function Interface for Python calling C code." category = "main" optional = false -python-versions = "*" +python-versions = ">=3.8" files = [ - {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, - {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, - {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, - {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"}, - {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"}, - {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"}, - {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"}, - {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"}, - {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"}, - {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"}, - {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"}, - {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"}, - {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"}, - {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"}, - {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"}, - {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"}, - {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"}, - {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"}, - {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"}, - {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"}, - {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"}, - {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"}, - {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"}, - {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"}, - {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"}, - {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"}, - {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"}, - {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"}, - {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"}, - {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"}, - {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"}, - {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"}, - {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"}, - {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"}, - {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"}, - {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"}, - {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"}, - {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"}, - {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"}, - {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, - {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, + {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"}, + {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"}, + {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"}, + {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"}, + {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"}, + {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"}, + {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"}, + {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"}, + {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"}, + {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"}, + {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"}, + {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"}, + {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"}, + {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"}, + {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"}, + {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"}, + {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"}, + {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"}, + {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"}, + {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"}, + {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"}, ] [package.dependencies] @@ -259,87 +267,102 @@ files = [ [[package]] name = "charset-normalizer" -version = "3.2.0" +version = "3.3.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "main" optional = false python-versions = ">=3.7.0" files = [ - {file = "charset-normalizer-3.2.0.tar.gz", hash = "sha256:3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-win32.whl", hash = "sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96"}, - {file = "charset_normalizer-3.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:48021783bdf96e3d6de03a6e39a1171ed5bd7e8bb93fc84cc649d11490f87cea"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-win32.whl", hash = "sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1"}, - {file = "charset_normalizer-3.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:681eb3d7e02e3c3655d1b16059fbfb605ac464c834a0c629048a30fad2b27489"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-win32.whl", hash = "sha256:a401b4598e5d3f4a9a811f3daf42ee2291790c7f9d74b18d75d6e21dda98a1a1"}, - {file = "charset_normalizer-3.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c0b21078a4b56965e2b12f247467b234734491897e99c1d51cee628da9786959"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-win32.whl", hash = "sha256:1000fba1057b92a65daec275aec30586c3de2401ccdcd41f8a5c1e2c87078706"}, - {file = "charset_normalizer-3.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:8b2c760cfc7042b27ebdb4a43a4453bd829a5742503599144d54a032c5dc7e9e"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-win32.whl", hash = "sha256:6c409c0deba34f147f77efaa67b8e4bb83d2f11c8806405f76397ae5b8c0d1c9"}, - {file = "charset_normalizer-3.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:7095f6fbfaa55defb6b733cfeb14efaae7a29f0b59d8cf213be4e7ca0b857b80"}, - {file = "charset_normalizer-3.2.0-py3-none-any.whl", hash = "sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6"}, + {file = "charset-normalizer-3.3.0.tar.gz", hash = "sha256:63563193aec44bce707e0c5ca64ff69fa72ed7cf34ce6e11d5127555756fd2f6"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:effe5406c9bd748a871dbcaf3ac69167c38d72db8c9baf3ff954c344f31c4cbe"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4162918ef3098851fcd8a628bf9b6a98d10c380725df9e04caf5ca6dd48c847a"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0570d21da019941634a531444364f2482e8db0b3425fcd5ac0c36565a64142c8"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5707a746c6083a3a74b46b3a631d78d129edab06195a92a8ece755aac25a3f3d"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:278c296c6f96fa686d74eb449ea1697f3c03dc28b75f873b65b5201806346a69"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a4b71f4d1765639372a3b32d2638197f5cd5221b19531f9245fcc9ee62d38f56"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5969baeaea61c97efa706b9b107dcba02784b1601c74ac84f2a532ea079403e"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3f93dab657839dfa61025056606600a11d0b696d79386f974e459a3fbc568ec"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:db756e48f9c5c607b5e33dd36b1d5872d0422e960145b08ab0ec7fd420e9d649"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:232ac332403e37e4a03d209a3f92ed9071f7d3dbda70e2a5e9cff1c4ba9f0678"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e5c1502d4ace69a179305abb3f0bb6141cbe4714bc9b31d427329a95acfc8bdd"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:2502dd2a736c879c0f0d3e2161e74d9907231e25d35794584b1ca5284e43f596"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23e8565ab7ff33218530bc817922fae827420f143479b753104ab801145b1d5b"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-win32.whl", hash = "sha256:1872d01ac8c618a8da634e232f24793883d6e456a66593135aeafe3784b0848d"}, + {file = "charset_normalizer-3.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:557b21a44ceac6c6b9773bc65aa1b4cc3e248a5ad2f5b914b91579a32e22204d"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d7eff0f27edc5afa9e405f7165f85a6d782d308f3b6b9d96016c010597958e63"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a685067d05e46641d5d1623d7c7fdf15a357546cbb2f71b0ebde91b175ffc3e"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0d3d5b7db9ed8a2b11a774db2bbea7ba1884430a205dbd54a32d61d7c2a190fa"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2935ffc78db9645cb2086c2f8f4cfd23d9b73cc0dc80334bc30aac6f03f68f8c"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fe359b2e3a7729010060fbca442ca225280c16e923b37db0e955ac2a2b72a05"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:380c4bde80bce25c6e4f77b19386f5ec9db230df9f2f2ac1e5ad7af2caa70459"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0d1e3732768fecb052d90d62b220af62ead5748ac51ef61e7b32c266cac9293"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1b2919306936ac6efb3aed1fbf81039f7087ddadb3160882a57ee2ff74fd2382"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f8888e31e3a85943743f8fc15e71536bda1c81d5aa36d014a3c0c44481d7db6e"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:82eb849f085624f6a607538ee7b83a6d8126df6d2f7d3b319cb837b289123078"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7b8b8bf1189b3ba9b8de5c8db4d541b406611a71a955bbbd7385bbc45fcb786c"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5adf257bd58c1b8632046bbe43ee38c04e1038e9d37de9c57a94d6bd6ce5da34"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c350354efb159b8767a6244c166f66e67506e06c8924ed74669b2c70bc8735b1"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-win32.whl", hash = "sha256:02af06682e3590ab952599fbadac535ede5d60d78848e555aa58d0c0abbde786"}, + {file = "charset_normalizer-3.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:86d1f65ac145e2c9ed71d8ffb1905e9bba3a91ae29ba55b4c46ae6fc31d7c0d4"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:3b447982ad46348c02cb90d230b75ac34e9886273df3a93eec0539308a6296d7"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:abf0d9f45ea5fb95051c8bfe43cb40cda383772f7e5023a83cc481ca2604d74e"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b09719a17a2301178fac4470d54b1680b18a5048b481cb8890e1ef820cb80455"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b3d9b48ee6e3967b7901c052b670c7dda6deb812c309439adaffdec55c6d7b78"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:edfe077ab09442d4ef3c52cb1f9dab89bff02f4524afc0acf2d46be17dc479f5"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3debd1150027933210c2fc321527c2299118aa929c2f5a0a80ab6953e3bd1908"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86f63face3a527284f7bb8a9d4f78988e3c06823f7bea2bd6f0e0e9298ca0403"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:24817cb02cbef7cd499f7c9a2735286b4782bd47a5b3516a0e84c50eab44b98e"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c71f16da1ed8949774ef79f4a0260d28b83b3a50c6576f8f4f0288d109777989"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:9cf3126b85822c4e53aa28c7ec9869b924d6fcfb76e77a45c44b83d91afd74f9"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:b3b2316b25644b23b54a6f6401074cebcecd1244c0b8e80111c9a3f1c8e83d65"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:03680bb39035fbcffe828eae9c3f8afc0428c91d38e7d61aa992ef7a59fb120e"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4cc152c5dd831641e995764f9f0b6589519f6f5123258ccaca8c6d34572fefa8"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-win32.whl", hash = "sha256:b8f3307af845803fb0b060ab76cf6dd3a13adc15b6b451f54281d25911eb92df"}, + {file = "charset_normalizer-3.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:8eaf82f0eccd1505cf39a45a6bd0a8cf1c70dcfc30dba338207a969d91b965c0"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dc45229747b67ffc441b3de2f3ae5e62877a282ea828a5bdb67883c4ee4a8810"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f4a0033ce9a76e391542c182f0d48d084855b5fcba5010f707c8e8c34663d77"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ada214c6fa40f8d800e575de6b91a40d0548139e5dc457d2ebb61470abf50186"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b1121de0e9d6e6ca08289583d7491e7fcb18a439305b34a30b20d8215922d43c"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1063da2c85b95f2d1a430f1c33b55c9c17ffaf5e612e10aeaad641c55a9e2b9d"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70f1d09c0d7748b73290b29219e854b3207aea922f839437870d8cc2168e31cc"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:250c9eb0f4600361dd80d46112213dff2286231d92d3e52af1e5a6083d10cad9"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:750b446b2ffce1739e8578576092179160f6d26bd5e23eb1789c4d64d5af7dc7"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:fc52b79d83a3fe3a360902d3f5d79073a993597d48114c29485e9431092905d8"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:588245972aca710b5b68802c8cad9edaa98589b1b42ad2b53accd6910dad3545"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e39c7eb31e3f5b1f88caff88bcff1b7f8334975b46f6ac6e9fc725d829bc35d4"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-win32.whl", hash = "sha256:abecce40dfebbfa6abf8e324e1860092eeca6f7375c8c4e655a8afb61af58f2c"}, + {file = "charset_normalizer-3.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:24a91a981f185721542a0b7c92e9054b7ab4fea0508a795846bc5b0abf8118d4"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:67b8cc9574bb518ec76dc8e705d4c39ae78bb96237cb533edac149352c1f39fe"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ac71b2977fb90c35d41c9453116e283fac47bb9096ad917b8819ca8b943abecd"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3ae38d325b512f63f8da31f826e6cb6c367336f95e418137286ba362925c877e"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:542da1178c1c6af8873e143910e2269add130a299c9106eef2594e15dae5e482"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:30a85aed0b864ac88309b7d94be09f6046c834ef60762a8833b660139cfbad13"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aae32c93e0f64469f74ccc730a7cb21c7610af3a775157e50bbd38f816536b38"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15b26ddf78d57f1d143bdf32e820fd8935d36abe8a25eb9ec0b5a71c82eb3895"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f5d10bae5d78e4551b7be7a9b29643a95aded9d0f602aa2ba584f0388e7a557"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:249c6470a2b60935bafd1d1d13cd613f8cd8388d53461c67397ee6a0f5dce741"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c5a74c359b2d47d26cdbbc7845e9662d6b08a1e915eb015d044729e92e7050b7"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:b5bcf60a228acae568e9911f410f9d9e0d43197d030ae5799e20dca8df588287"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:187d18082694a29005ba2944c882344b6748d5be69e3a89bf3cc9d878e548d5a"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:81bf654678e575403736b85ba3a7867e31c2c30a69bc57fe88e3ace52fb17b89"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-win32.whl", hash = "sha256:85a32721ddde63c9df9ebb0d2045b9691d9750cb139c161c80e500d210f5e26e"}, + {file = "charset_normalizer-3.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:468d2a840567b13a590e67dd276c570f8de00ed767ecc611994c301d0f8c014f"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e0fc42822278451bc13a2e8626cf2218ba570f27856b536e00cfa53099724828"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:09c77f964f351a7369cc343911e0df63e762e42bac24cd7d18525961c81754f4"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:12ebea541c44fdc88ccb794a13fe861cc5e35d64ed689513a5c03d05b53b7c82"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:805dfea4ca10411a5296bcc75638017215a93ffb584c9e344731eef0dcfb026a"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:96c2b49eb6a72c0e4991d62406e365d87067ca14c1a729a870d22354e6f68115"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aaf7b34c5bc56b38c931a54f7952f1ff0ae77a2e82496583b247f7c969eb1479"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:619d1c96099be5823db34fe89e2582b336b5b074a7f47f819d6b3a57ff7bdb86"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0ac5e7015a5920cfce654c06618ec40c33e12801711da6b4258af59a8eff00a"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:93aa7eef6ee71c629b51ef873991d6911b906d7312c6e8e99790c0f33c576f89"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7966951325782121e67c81299a031f4c115615e68046f79b85856b86ebffc4cd"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:02673e456dc5ab13659f85196c534dc596d4ef260e4d86e856c3b2773ce09843"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:c2af80fb58f0f24b3f3adcb9148e6203fa67dd3f61c4af146ecad033024dde43"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:153e7b6e724761741e0974fc4dcd406d35ba70b92bfe3fedcb497226c93b9da7"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-win32.whl", hash = "sha256:d47ecf253780c90ee181d4d871cd655a789da937454045b17b5798da9393901a"}, + {file = "charset_normalizer-3.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:d97d85fa63f315a8bdaba2af9a6a686e0eceab77b3089af45133252618e70884"}, + {file = "charset_normalizer-3.3.0-py3-none-any.whl", hash = "sha256:e46cd37076971c1040fc8c41273a8b3e2c624ce4f2be3f5dfcb7a430c1d3acc2"}, ] [[package]] @@ -427,64 +450,64 @@ files = [ [[package]] name = "coverage" -version = "7.3.0" +version = "7.3.2" description = "Code coverage measurement for Python" category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:db76a1bcb51f02b2007adacbed4c88b6dee75342c37b05d1822815eed19edee5"}, - {file = "coverage-7.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c02cfa6c36144ab334d556989406837336c1d05215a9bdf44c0bc1d1ac1cb637"}, - {file = "coverage-7.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:477c9430ad5d1b80b07f3c12f7120eef40bfbf849e9e7859e53b9c93b922d2af"}, - {file = "coverage-7.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce2ee86ca75f9f96072295c5ebb4ef2a43cecf2870b0ca5e7a1cbdd929cf67e1"}, - {file = "coverage-7.3.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68d8a0426b49c053013e631c0cdc09b952d857efa8f68121746b339912d27a12"}, - {file = "coverage-7.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b3eb0c93e2ea6445b2173da48cb548364f8f65bf68f3d090404080d338e3a689"}, - {file = "coverage-7.3.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:90b6e2f0f66750c5a1178ffa9370dec6c508a8ca5265c42fbad3ccac210a7977"}, - {file = "coverage-7.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:96d7d761aea65b291a98c84e1250cd57b5b51726821a6f2f8df65db89363be51"}, - {file = "coverage-7.3.0-cp310-cp310-win32.whl", hash = "sha256:63c5b8ecbc3b3d5eb3a9d873dec60afc0cd5ff9d9f1c75981d8c31cfe4df8527"}, - {file = "coverage-7.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:97c44f4ee13bce914272589b6b41165bbb650e48fdb7bd5493a38bde8de730a1"}, - {file = "coverage-7.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:74c160285f2dfe0acf0f72d425f3e970b21b6de04157fc65adc9fd07ee44177f"}, - {file = "coverage-7.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b543302a3707245d454fc49b8ecd2c2d5982b50eb63f3535244fd79a4be0c99d"}, - {file = "coverage-7.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad0f87826c4ebd3ef484502e79b39614e9c03a5d1510cfb623f4a4a051edc6fd"}, - {file = "coverage-7.3.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:13c6cbbd5f31211d8fdb477f0f7b03438591bdd077054076eec362cf2207b4a7"}, - {file = "coverage-7.3.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fac440c43e9b479d1241fe9d768645e7ccec3fb65dc3a5f6e90675e75c3f3e3a"}, - {file = "coverage-7.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:3c9834d5e3df9d2aba0275c9f67989c590e05732439b3318fa37a725dff51e74"}, - {file = "coverage-7.3.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4c8e31cf29b60859876474034a83f59a14381af50cbe8a9dbaadbf70adc4b214"}, - {file = "coverage-7.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7a9baf8e230f9621f8e1d00c580394a0aa328fdac0df2b3f8384387c44083c0f"}, - {file = "coverage-7.3.0-cp311-cp311-win32.whl", hash = "sha256:ccc51713b5581e12f93ccb9c5e39e8b5d4b16776d584c0f5e9e4e63381356482"}, - {file = "coverage-7.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:887665f00ea4e488501ba755a0e3c2cfd6278e846ada3185f42d391ef95e7e70"}, - {file = "coverage-7.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d000a739f9feed900381605a12a61f7aaced6beae832719ae0d15058a1e81c1b"}, - {file = "coverage-7.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:59777652e245bb1e300e620ce2bef0d341945842e4eb888c23a7f1d9e143c446"}, - {file = "coverage-7.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9737bc49a9255d78da085fa04f628a310c2332b187cd49b958b0e494c125071"}, - {file = "coverage-7.3.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5247bab12f84a1d608213b96b8af0cbb30d090d705b6663ad794c2f2a5e5b9fe"}, - {file = "coverage-7.3.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2ac9a1de294773b9fa77447ab7e529cf4fe3910f6a0832816e5f3d538cfea9a"}, - {file = "coverage-7.3.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:85b7335c22455ec12444cec0d600533a238d6439d8d709d545158c1208483873"}, - {file = "coverage-7.3.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:36ce5d43a072a036f287029a55b5c6a0e9bd73db58961a273b6dc11a2c6eb9c2"}, - {file = "coverage-7.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:211a4576e984f96d9fce61766ffaed0115d5dab1419e4f63d6992b480c2bd60b"}, - {file = "coverage-7.3.0-cp312-cp312-win32.whl", hash = "sha256:56afbf41fa4a7b27f6635bc4289050ac3ab7951b8a821bca46f5b024500e6321"}, - {file = "coverage-7.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:7f297e0c1ae55300ff688568b04ff26b01c13dfbf4c9d2b7d0cb688ac60df479"}, - {file = "coverage-7.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ac0dec90e7de0087d3d95fa0533e1d2d722dcc008bc7b60e1143402a04c117c1"}, - {file = "coverage-7.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:438856d3f8f1e27f8e79b5410ae56650732a0dcfa94e756df88c7e2d24851fcd"}, - {file = "coverage-7.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1084393c6bda8875c05e04fce5cfe1301a425f758eb012f010eab586f1f3905e"}, - {file = "coverage-7.3.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49ab200acf891e3dde19e5aa4b0f35d12d8b4bd805dc0be8792270c71bd56c54"}, - {file = "coverage-7.3.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a67e6bbe756ed458646e1ef2b0778591ed4d1fcd4b146fc3ba2feb1a7afd4254"}, - {file = "coverage-7.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8f39c49faf5344af36042b293ce05c0d9004270d811c7080610b3e713251c9b0"}, - {file = "coverage-7.3.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7df91fb24c2edaabec4e0eee512ff3bc6ec20eb8dccac2e77001c1fe516c0c84"}, - {file = "coverage-7.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:34f9f0763d5fa3035a315b69b428fe9c34d4fc2f615262d6be3d3bf3882fb985"}, - {file = "coverage-7.3.0-cp38-cp38-win32.whl", hash = "sha256:bac329371d4c0d456e8d5f38a9b0816b446581b5f278474e416ea0c68c47dcd9"}, - {file = "coverage-7.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:b859128a093f135b556b4765658d5d2e758e1fae3e7cc2f8c10f26fe7005e543"}, - {file = "coverage-7.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fc0ed8d310afe013db1eedd37176d0839dc66c96bcfcce8f6607a73ffea2d6ba"}, - {file = "coverage-7.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61260ec93f99f2c2d93d264b564ba912bec502f679793c56f678ba5251f0393"}, - {file = "coverage-7.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97af9554a799bd7c58c0179cc8dbf14aa7ab50e1fd5fa73f90b9b7215874ba28"}, - {file = "coverage-7.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3558e5b574d62f9c46b76120a5c7c16c4612dc2644c3d48a9f4064a705eaee95"}, - {file = "coverage-7.3.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37d5576d35fcb765fca05654f66aa71e2808d4237d026e64ac8b397ffa66a56a"}, - {file = "coverage-7.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:07ea61bcb179f8f05ffd804d2732b09d23a1238642bf7e51dad62082b5019b34"}, - {file = "coverage-7.3.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:80501d1b2270d7e8daf1b64b895745c3e234289e00d5f0e30923e706f110334e"}, - {file = "coverage-7.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4eddd3153d02204f22aef0825409091a91bf2a20bce06fe0f638f5c19a85de54"}, - {file = "coverage-7.3.0-cp39-cp39-win32.whl", hash = "sha256:2d22172f938455c156e9af2612650f26cceea47dc86ca048fa4e0b2d21646ad3"}, - {file = "coverage-7.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:60f64e2007c9144375dd0f480a54d6070f00bb1a28f65c408370544091c9bc9e"}, - {file = "coverage-7.3.0-pp38.pp39.pp310-none-any.whl", hash = "sha256:5492a6ce3bdb15c6ad66cb68a0244854d9917478877a25671d70378bdc8562d0"}, - {file = "coverage-7.3.0.tar.gz", hash = "sha256:49dbb19cdcafc130f597d9e04a29d0a032ceedf729e41b181f51cd170e6ee865"}, + {file = "coverage-7.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d872145f3a3231a5f20fd48500274d7df222e291d90baa2026cc5152b7ce86bf"}, + {file = "coverage-7.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:310b3bb9c91ea66d59c53fa4989f57d2436e08f18fb2f421a1b0b6b8cc7fffda"}, + {file = "coverage-7.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f47d39359e2c3779c5331fc740cf4bce6d9d680a7b4b4ead97056a0ae07cb49a"}, + {file = "coverage-7.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa72dbaf2c2068404b9870d93436e6d23addd8bbe9295f49cbca83f6e278179c"}, + {file = "coverage-7.3.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:beaa5c1b4777f03fc63dfd2a6bd820f73f036bfb10e925fce067b00a340d0f3f"}, + {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:dbc1b46b92186cc8074fee9d9fbb97a9dd06c6cbbef391c2f59d80eabdf0faa6"}, + {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:315a989e861031334d7bee1f9113c8770472db2ac484e5b8c3173428360a9148"}, + {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d1bc430677773397f64a5c88cb522ea43175ff16f8bfcc89d467d974cb2274f9"}, + {file = "coverage-7.3.2-cp310-cp310-win32.whl", hash = "sha256:a889ae02f43aa45032afe364c8ae84ad3c54828c2faa44f3bfcafecb5c96b02f"}, + {file = "coverage-7.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c0ba320de3fb8c6ec16e0be17ee1d3d69adcda99406c43c0409cb5c41788a611"}, + {file = "coverage-7.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ac8c802fa29843a72d32ec56d0ca792ad15a302b28ca6203389afe21f8fa062c"}, + {file = "coverage-7.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:89a937174104339e3a3ffcf9f446c00e3a806c28b1841c63edb2b369310fd074"}, + {file = "coverage-7.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e267e9e2b574a176ddb983399dec325a80dbe161f1a32715c780b5d14b5f583a"}, + {file = "coverage-7.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2443cbda35df0d35dcfb9bf8f3c02c57c1d6111169e3c85fc1fcc05e0c9f39a3"}, + {file = "coverage-7.3.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4175e10cc8dda0265653e8714b3174430b07c1dca8957f4966cbd6c2b1b8065a"}, + {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0cbf38419fb1a347aaf63481c00f0bdc86889d9fbf3f25109cf96c26b403fda1"}, + {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5c913b556a116b8d5f6ef834038ba983834d887d82187c8f73dec21049abd65c"}, + {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1981f785239e4e39e6444c63a98da3a1db8e971cb9ceb50a945ba6296b43f312"}, + {file = "coverage-7.3.2-cp311-cp311-win32.whl", hash = "sha256:43668cabd5ca8258f5954f27a3aaf78757e6acf13c17604d89648ecc0cc66640"}, + {file = "coverage-7.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10c39c0452bf6e694511c901426d6b5ac005acc0f78ff265dbe36bf81f808a2"}, + {file = "coverage-7.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4cbae1051ab791debecc4a5dcc4a1ff45fc27b91b9aee165c8a27514dd160836"}, + {file = "coverage-7.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12d15ab5833a997716d76f2ac1e4b4d536814fc213c85ca72756c19e5a6b3d63"}, + {file = "coverage-7.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c7bba973ebee5e56fe9251300c00f1579652587a9f4a5ed8404b15a0471f216"}, + {file = "coverage-7.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe494faa90ce6381770746077243231e0b83ff3f17069d748f645617cefe19d4"}, + {file = "coverage-7.3.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6e9589bd04d0461a417562649522575d8752904d35c12907d8c9dfeba588faf"}, + {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d51ac2a26f71da1b57f2dc81d0e108b6ab177e7d30e774db90675467c847bbdf"}, + {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:99b89d9f76070237975b315b3d5f4d6956ae354a4c92ac2388a5695516e47c84"}, + {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fa28e909776dc69efb6ed975a63691bc8172b64ff357e663a1bb06ff3c9b589a"}, + {file = "coverage-7.3.2-cp312-cp312-win32.whl", hash = "sha256:289fe43bf45a575e3ab10b26d7b6f2ddb9ee2dba447499f5401cfb5ecb8196bb"}, + {file = "coverage-7.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7dbc3ed60e8659bc59b6b304b43ff9c3ed858da2839c78b804973f613d3e92ed"}, + {file = "coverage-7.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f94b734214ea6a36fe16e96a70d941af80ff3bfd716c141300d95ebc85339738"}, + {file = "coverage-7.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:af3d828d2c1cbae52d34bdbb22fcd94d1ce715d95f1a012354a75e5913f1bda2"}, + {file = "coverage-7.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:630b13e3036e13c7adc480ca42fa7afc2a5d938081d28e20903cf7fd687872e2"}, + {file = "coverage-7.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9eacf273e885b02a0273bb3a2170f30e2d53a6d53b72dbe02d6701b5296101c"}, + {file = "coverage-7.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8f17966e861ff97305e0801134e69db33b143bbfb36436efb9cfff6ec7b2fd9"}, + {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b4275802d16882cf9c8b3d057a0839acb07ee9379fa2749eca54efbce1535b82"}, + {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:72c0cfa5250f483181e677ebc97133ea1ab3eb68645e494775deb6a7f6f83901"}, + {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cb536f0dcd14149425996821a168f6e269d7dcd2c273a8bff8201e79f5104e76"}, + {file = "coverage-7.3.2-cp38-cp38-win32.whl", hash = "sha256:307adb8bd3abe389a471e649038a71b4eb13bfd6b7dd9a129fa856f5c695cf92"}, + {file = "coverage-7.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:88ed2c30a49ea81ea3b7f172e0269c182a44c236eb394718f976239892c0a27a"}, + {file = "coverage-7.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b631c92dfe601adf8f5ebc7fc13ced6bb6e9609b19d9a8cd59fa47c4186ad1ce"}, + {file = "coverage-7.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d3d9df4051c4a7d13036524b66ecf7a7537d14c18a384043f30a303b146164e9"}, + {file = "coverage-7.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f7363d3b6a1119ef05015959ca24a9afc0ea8a02c687fe7e2d557705375c01f"}, + {file = "coverage-7.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f11cc3c967a09d3695d2a6f03fb3e6236622b93be7a4b5dc09166a861be6d25"}, + {file = "coverage-7.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:149de1d2401ae4655c436a3dced6dd153f4c3309f599c3d4bd97ab172eaf02d9"}, + {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3a4006916aa6fee7cd38db3bfc95aa9c54ebb4ffbfc47c677c8bba949ceba0a6"}, + {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9028a3871280110d6e1aa2df1afd5ef003bab5fb1ef421d6dc748ae1c8ef2ebc"}, + {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9f805d62aec8eb92bab5b61c0f07329275b6f41c97d80e847b03eb894f38d083"}, + {file = "coverage-7.3.2-cp39-cp39-win32.whl", hash = "sha256:d1c88ec1a7ff4ebca0219f5b1ef863451d828cccf889c173e1253aa84b1e07ce"}, + {file = "coverage-7.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b4767da59464bb593c07afceaddea61b154136300881844768037fd5e859353f"}, + {file = "coverage-7.3.2-pp38.pp39.pp310-none-any.whl", hash = "sha256:ae97af89f0fbf373400970c0a21eef5aa941ffeed90aee43650b81f7d7f47637"}, + {file = "coverage-7.3.2.tar.gz", hash = "sha256:be32ad29341b0170e795ca590e1c07e81fc061cb5b10c74ce7203491484404ef"}, ] [package.dependencies] @@ -495,35 +518,35 @@ toml = ["tomli"] [[package]] name = "cryptography" -version = "41.0.3" +version = "41.0.4" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-41.0.3-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:652627a055cb52a84f8c448185922241dd5217443ca194d5739b44612c5e6507"}, - {file = "cryptography-41.0.3-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:8f09daa483aedea50d249ef98ed500569841d6498aa9c9f4b0531b9964658922"}, - {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fd871184321100fb400d759ad0cddddf284c4b696568204d281c902fc7b0d81"}, - {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84537453d57f55a50a5b6835622ee405816999a7113267739a1b4581f83535bd"}, - {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:3fb248989b6363906827284cd20cca63bb1a757e0a2864d4c1682a985e3dca47"}, - {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:42cb413e01a5d36da9929baa9d70ca90d90b969269e5a12d39c1e0d475010116"}, - {file = "cryptography-41.0.3-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:aeb57c421b34af8f9fe830e1955bf493a86a7996cc1338fe41b30047d16e962c"}, - {file = "cryptography-41.0.3-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6af1c6387c531cd364b72c28daa29232162010d952ceb7e5ca8e2827526aceae"}, - {file = "cryptography-41.0.3-cp37-abi3-win32.whl", hash = "sha256:0d09fb5356f975974dbcb595ad2d178305e5050656affb7890a1583f5e02a306"}, - {file = "cryptography-41.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:a983e441a00a9d57a4d7c91b3116a37ae602907a7618b882c8013b5762e80574"}, - {file = "cryptography-41.0.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5259cb659aa43005eb55a0e4ff2c825ca111a0da1814202c64d28a985d33b087"}, - {file = "cryptography-41.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:67e120e9a577c64fe1f611e53b30b3e69744e5910ff3b6e97e935aeb96005858"}, - {file = "cryptography-41.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:7efe8041897fe7a50863e51b77789b657a133c75c3b094e51b5e4b5cec7bf906"}, - {file = "cryptography-41.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ce785cf81a7bdade534297ef9e490ddff800d956625020ab2ec2780a556c313e"}, - {file = "cryptography-41.0.3-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:57a51b89f954f216a81c9d057bf1a24e2f36e764a1ca9a501a6964eb4a6800dd"}, - {file = "cryptography-41.0.3-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:4c2f0d35703d61002a2bbdcf15548ebb701cfdd83cdc12471d2bae80878a4207"}, - {file = "cryptography-41.0.3-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:23c2d778cf829f7d0ae180600b17e9fceea3c2ef8b31a99e3c694cbbf3a24b84"}, - {file = "cryptography-41.0.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:95dd7f261bb76948b52a5330ba5202b91a26fbac13ad0e9fc8a3ac04752058c7"}, - {file = "cryptography-41.0.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:41d7aa7cdfded09b3d73a47f429c298e80796c8e825ddfadc84c8a7f12df212d"}, - {file = "cryptography-41.0.3-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d0d651aa754ef58d75cec6edfbd21259d93810b73f6ec246436a21b7841908de"}, - {file = "cryptography-41.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:ab8de0d091acbf778f74286f4989cf3d1528336af1b59f3e5d2ebca8b5fe49e1"}, - {file = "cryptography-41.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a74fbcdb2a0d46fe00504f571a2a540532f4c188e6ccf26f1f178480117b33c4"}, - {file = "cryptography-41.0.3.tar.gz", hash = "sha256:6d192741113ef5e30d89dcb5b956ef4e1578f304708701b8b73d38e3e1461f34"}, + {file = "cryptography-41.0.4-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:80907d3faa55dc5434a16579952ac6da800935cd98d14dbd62f6f042c7f5e839"}, + {file = "cryptography-41.0.4-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:35c00f637cd0b9d5b6c6bd11b6c3359194a8eba9c46d4e875a3660e3b400005f"}, + {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cecfefa17042941f94ab54f769c8ce0fe14beff2694e9ac684176a2535bf9714"}, + {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e40211b4923ba5a6dc9769eab704bdb3fbb58d56c5b336d30996c24fcf12aadb"}, + {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:23a25c09dfd0d9f28da2352503b23e086f8e78096b9fd585d1d14eca01613e13"}, + {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2ed09183922d66c4ec5fdaa59b4d14e105c084dd0febd27452de8f6f74704143"}, + {file = "cryptography-41.0.4-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:5a0f09cefded00e648a127048119f77bc2b2ec61e736660b5789e638f43cc397"}, + {file = "cryptography-41.0.4-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:9eeb77214afae972a00dee47382d2591abe77bdae166bda672fb1e24702a3860"}, + {file = "cryptography-41.0.4-cp37-abi3-win32.whl", hash = "sha256:3b224890962a2d7b57cf5eeb16ccaafba6083f7b811829f00476309bce2fe0fd"}, + {file = "cryptography-41.0.4-cp37-abi3-win_amd64.whl", hash = "sha256:c880eba5175f4307129784eca96f4e70b88e57aa3f680aeba3bab0e980b0f37d"}, + {file = "cryptography-41.0.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:004b6ccc95943f6a9ad3142cfabcc769d7ee38a3f60fb0dddbfb431f818c3a67"}, + {file = "cryptography-41.0.4-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:86defa8d248c3fa029da68ce61fe735432b047e32179883bdb1e79ed9bb8195e"}, + {file = "cryptography-41.0.4-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:37480760ae08065437e6573d14be973112c9e6dcaf5f11d00147ee74f37a3829"}, + {file = "cryptography-41.0.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b5f4dfe950ff0479f1f00eda09c18798d4f49b98f4e2006d644b3301682ebdca"}, + {file = "cryptography-41.0.4-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7e53db173370dea832190870e975a1e09c86a879b613948f09eb49324218c14d"}, + {file = "cryptography-41.0.4-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5b72205a360f3b6176485a333256b9bcd48700fc755fef51c8e7e67c4b63e3ac"}, + {file = "cryptography-41.0.4-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:93530900d14c37a46ce3d6c9e6fd35dbe5f5601bf6b3a5c325c7bffc030344d9"}, + {file = "cryptography-41.0.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:efc8ad4e6fc4f1752ebfb58aefece8b4e3c4cae940b0994d43649bdfce8d0d4f"}, + {file = "cryptography-41.0.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c3391bd8e6de35f6f1140e50aaeb3e2b3d6a9012536ca23ab0d9c35ec18c8a91"}, + {file = "cryptography-41.0.4-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:0d9409894f495d465fe6fda92cb70e8323e9648af912d5b9141d616df40a87b8"}, + {file = "cryptography-41.0.4-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:8ac4f9ead4bbd0bc8ab2d318f97d85147167a488be0e08814a37eb2f439d5cf6"}, + {file = "cryptography-41.0.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:047c4603aeb4bbd8db2756e38f5b8bd7e94318c047cfe4efeb5d715e08b49311"}, + {file = "cryptography-41.0.4.tar.gz", hash = "sha256:7febc3094125fc126a7f6fb1f420d0da639f3f32cb15c8ff0dc3997c4549f51a"}, ] [package.dependencies] @@ -609,14 +632,14 @@ test = ["pytest (>=6)"] [[package]] name = "fastapi" -version = "0.101.1" +version = "0.103.0" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "fastapi-0.101.1-py3-none-any.whl", hash = "sha256:aef5f8676eb1b8389952e1fe734abe20f04b71f6936afcc53b320ba79b686a4b"}, - {file = "fastapi-0.101.1.tar.gz", hash = "sha256:7b32000d14ca9992f7461117b81e4ef9ff0c07936af641b4fe40e67d5f9d63cb"}, + {file = "fastapi-0.103.0-py3-none-any.whl", hash = "sha256:61ab72c6c281205dd0cbaccf503e829a37e0be108d965ac223779a8479243665"}, + {file = "fastapi-0.103.0.tar.gz", hash = "sha256:4166732f5ddf61c33e9fa4664f73780872511e0598d4d5434b1816dc1e6d9421"}, ] [package.dependencies] @@ -629,19 +652,20 @@ all = ["email-validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)" [[package]] name = "filelock" -version = "3.12.2" +version = "3.12.4" description = "A platform independent file lock." category = "dev" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "filelock-3.12.2-py3-none-any.whl", hash = "sha256:cbb791cdea2a72f23da6ac5b5269ab0a0d161e9ef0100e653b69049a7706d1ec"}, - {file = "filelock-3.12.2.tar.gz", hash = "sha256:002740518d8aa59a26b0c76e10fb8c6e15eae825d34b6fdf670333fd7b938d81"}, + {file = "filelock-3.12.4-py3-none-any.whl", hash = "sha256:08c21d87ded6e2b9da6728c3dff51baf1dcecf973b768ef35bcbc3447edb9ad4"}, + {file = "filelock-3.12.4.tar.gz", hash = "sha256:2e6f249f1f3654291606e046b09f1fd5eac39b360664c27f5aad072012f8bcbd"}, ] [package.extras] -docs = ["furo (>=2023.5.20)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "diff-cover (>=7.5)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)", "pytest-timeout (>=2.1)"] +docs = ["furo (>=2023.7.26)", "sphinx (>=7.1.2)", "sphinx-autodoc-typehints (>=1.24)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.3)", "diff-cover (>=7.7)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)", "pytest-timeout (>=2.1)"] +typing = ["typing-extensions (>=4.7.1)"] [[package]] name = "h11" @@ -657,14 +681,14 @@ files = [ [[package]] name = "httpcore" -version = "0.17.3" +version = "0.18.0" description = "A minimal low-level HTTP client." category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "httpcore-0.17.3-py3-none-any.whl", hash = "sha256:c2789b767ddddfa2a5782e3199b2b7f6894540b17b16ec26b2c4d8e103510b87"}, - {file = "httpcore-0.17.3.tar.gz", hash = "sha256:a6f30213335e34c1ade7be6ec7c47f19f50c56db36abef1a9dfa3815b1cb3888"}, + {file = "httpcore-0.18.0-py3-none-any.whl", hash = "sha256:adc5398ee0a476567bf87467063ee63584a8bce86078bf748e48754f60202ced"}, + {file = "httpcore-0.18.0.tar.gz", hash = "sha256:13b5e5cd1dca1a6636a6aaea212b19f4f85cd88c366a2b82304181b769aab3c9"}, ] [package.dependencies] @@ -679,19 +703,19 @@ socks = ["socksio (>=1.0.0,<2.0.0)"] [[package]] name = "httpx" -version = "0.24.1" +version = "0.25.0" description = "The next generation HTTP client." category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "httpx-0.24.1-py3-none-any.whl", hash = "sha256:06781eb9ac53cde990577af654bd990a4949de37a28bdb4a230d434f3a30b9bd"}, - {file = "httpx-0.24.1.tar.gz", hash = "sha256:5853a43053df830c20f8110c5e69fe44d035d850b2dfe795e196f00fdb774bdd"}, + {file = "httpx-0.25.0-py3-none-any.whl", hash = "sha256:181ea7f8ba3a82578be86ef4171554dd45fec26a02556a744db029a0a27b7100"}, + {file = "httpx-0.25.0.tar.gz", hash = "sha256:47ecda285389cb32bb2691cc6e069e3ab0205956f681c5b2ad2325719751d875"}, ] [package.dependencies] certifi = "*" -httpcore = ">=0.15.0,<0.18.0" +httpcore = ">=0.18.0,<0.19.0" idna = "*" sniffio = "*" @@ -703,14 +727,14 @@ socks = ["socksio (>=1.0.0,<2.0.0)"] [[package]] name = "identify" -version = "2.5.27" +version = "2.5.30" description = "File identification library for Python" category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "identify-2.5.27-py2.py3-none-any.whl", hash = "sha256:fdb527b2dfe24602809b2201e033c2a113d7bdf716db3ca8e3243f735dcecaba"}, - {file = "identify-2.5.27.tar.gz", hash = "sha256:287b75b04a0e22d727bc9a41f0d4f3c1bcada97490fa6eabb5b28f0e9097e733"}, + {file = "identify-2.5.30-py2.py3-none-any.whl", hash = "sha256:afe67f26ae29bab007ec21b03d4114f41316ab9dd15aa8736a167481e108da54"}, + {file = "identify-2.5.30.tar.gz", hash = "sha256:f302a4256a15c849b91cfcdcec052a8ce914634b2f77ae87dad29cd749f2d88d"}, ] [package.extras] @@ -762,14 +786,14 @@ files = [ [[package]] name = "loguru" -version = "0.7.0" +version = "0.7.2" description = "Python logging made (stupidly) simple" category = "main" optional = false python-versions = ">=3.5" files = [ - {file = "loguru-0.7.0-py3-none-any.whl", hash = "sha256:b93aa30099fa6860d4727f1b81f8718e965bb96253fa190fab2077aaad6d15d3"}, - {file = "loguru-0.7.0.tar.gz", hash = "sha256:1612053ced6ae84d7959dd7d5e431a0532642237ec21f7fd83ac73fe539e03e1"}, + {file = "loguru-0.7.2-py3-none-any.whl", hash = "sha256:003d71e3d3ed35f0f8984898359d65b79e5b21943f78af86aa5491210429b8eb"}, + {file = "loguru-0.7.2.tar.gz", hash = "sha256:e671a53522515f34fd406340ee968cb9ecafbc4b36c679da03c18fd8d0bd51ac"}, ] [package.dependencies] @@ -777,7 +801,7 @@ colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""} win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""} [package.extras] -dev = ["Sphinx (==5.3.0)", "colorama (==0.4.5)", "colorama (==0.4.6)", "freezegun (==1.1.0)", "freezegun (==1.2.2)", "mypy (==v0.910)", "mypy (==v0.971)", "mypy (==v0.990)", "pre-commit (==3.2.1)", "pytest (==6.1.2)", "pytest (==7.2.1)", "pytest-cov (==2.12.1)", "pytest-cov (==4.0.0)", "pytest-mypy-plugins (==1.10.1)", "pytest-mypy-plugins (==1.9.3)", "sphinx-autobuild (==2021.3.14)", "sphinx-rtd-theme (==1.2.0)", "tox (==3.27.1)", "tox (==4.4.6)"] +dev = ["Sphinx (==7.2.5)", "colorama (==0.4.5)", "colorama (==0.4.6)", "exceptiongroup (==1.1.3)", "freezegun (==1.1.0)", "freezegun (==1.2.2)", "mypy (==v0.910)", "mypy (==v0.971)", "mypy (==v1.4.1)", "mypy (==v1.5.1)", "pre-commit (==3.4.0)", "pytest (==6.1.2)", "pytest (==7.4.0)", "pytest-cov (==2.12.1)", "pytest-cov (==4.1.0)", "pytest-mypy-plugins (==1.9.3)", "pytest-mypy-plugins (==3.0.0)", "sphinx-autobuild (==2021.3.14)", "sphinx-rtd-theme (==1.3.0)", "tox (==3.27.1)", "tox (==4.11.0)"] [[package]] name = "marshmallow" @@ -814,39 +838,39 @@ files = [ [[package]] name = "mypy" -version = "1.5.1" +version = "1.6.0" description = "Optional static typing for Python" category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "mypy-1.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f33592ddf9655a4894aef22d134de7393e95fcbdc2d15c1ab65828eee5c66c70"}, - {file = "mypy-1.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:258b22210a4a258ccd077426c7a181d789d1121aca6db73a83f79372f5569ae0"}, - {file = "mypy-1.5.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9ec1f695f0c25986e6f7f8778e5ce61659063268836a38c951200c57479cc12"}, - {file = "mypy-1.5.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:abed92d9c8f08643c7d831300b739562b0a6c9fcb028d211134fc9ab20ccad5d"}, - {file = "mypy-1.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:a156e6390944c265eb56afa67c74c0636f10283429171018446b732f1a05af25"}, - {file = "mypy-1.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6ac9c21bfe7bc9f7f1b6fae441746e6a106e48fc9de530dea29e8cd37a2c0cc4"}, - {file = "mypy-1.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:51cb1323064b1099e177098cb939eab2da42fea5d818d40113957ec954fc85f4"}, - {file = "mypy-1.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:596fae69f2bfcb7305808c75c00f81fe2829b6236eadda536f00610ac5ec2243"}, - {file = "mypy-1.5.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:32cb59609b0534f0bd67faebb6e022fe534bdb0e2ecab4290d683d248be1b275"}, - {file = "mypy-1.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:159aa9acb16086b79bbb0016145034a1a05360626046a929f84579ce1666b315"}, - {file = "mypy-1.5.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f6b0e77db9ff4fda74de7df13f30016a0a663928d669c9f2c057048ba44f09bb"}, - {file = "mypy-1.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:26f71b535dfc158a71264e6dc805a9f8d2e60b67215ca0bfa26e2e1aa4d4d373"}, - {file = "mypy-1.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fc3a600f749b1008cc75e02b6fb3d4db8dbcca2d733030fe7a3b3502902f161"}, - {file = "mypy-1.5.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:26fb32e4d4afa205b24bf645eddfbb36a1e17e995c5c99d6d00edb24b693406a"}, - {file = "mypy-1.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:82cb6193de9bbb3844bab4c7cf80e6227d5225cc7625b068a06d005d861ad5f1"}, - {file = "mypy-1.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4a465ea2ca12804d5b34bb056be3a29dc47aea5973b892d0417c6a10a40b2d65"}, - {file = "mypy-1.5.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9fece120dbb041771a63eb95e4896791386fe287fefb2837258925b8326d6160"}, - {file = "mypy-1.5.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d28ddc3e3dfeab553e743e532fb95b4e6afad51d4706dd22f28e1e5e664828d2"}, - {file = "mypy-1.5.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:57b10c56016adce71fba6bc6e9fd45d8083f74361f629390c556738565af8eeb"}, - {file = "mypy-1.5.1-cp38-cp38-win_amd64.whl", hash = "sha256:ff0cedc84184115202475bbb46dd99f8dcb87fe24d5d0ddfc0fe6b8575c88d2f"}, - {file = "mypy-1.5.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8f772942d372c8cbac575be99f9cc9d9fb3bd95c8bc2de6c01411e2c84ebca8a"}, - {file = "mypy-1.5.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5d627124700b92b6bbaa99f27cbe615c8ea7b3402960f6372ea7d65faf376c14"}, - {file = "mypy-1.5.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:361da43c4f5a96173220eb53340ace68cda81845cd88218f8862dfb0adc8cddb"}, - {file = "mypy-1.5.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:330857f9507c24de5c5724235e66858f8364a0693894342485e543f5b07c8693"}, - {file = "mypy-1.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:c543214ffdd422623e9fedd0869166c2f16affe4ba37463975043ef7d2ea8770"}, - {file = "mypy-1.5.1-py3-none-any.whl", hash = "sha256:f757063a83970d67c444f6e01d9550a7402322af3557ce7630d3c957386fa8f5"}, - {file = "mypy-1.5.1.tar.gz", hash = "sha256:b031b9601f1060bf1281feab89697324726ba0c0bae9d7cd7ab4b690940f0b92"}, + {file = "mypy-1.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:091f53ff88cb093dcc33c29eee522c087a438df65eb92acd371161c1f4380ff0"}, + {file = "mypy-1.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:eb7ff4007865833c470a601498ba30462b7374342580e2346bf7884557e40531"}, + {file = "mypy-1.6.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49499cf1e464f533fc45be54d20a6351a312f96ae7892d8e9f1708140e27ce41"}, + {file = "mypy-1.6.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4c192445899c69f07874dabda7e931b0cc811ea055bf82c1ababf358b9b2a72c"}, + {file = "mypy-1.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:3df87094028e52766b0a59a3e46481bb98b27986ed6ded6a6cc35ecc75bb9182"}, + {file = "mypy-1.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c8835a07b8442da900db47ccfda76c92c69c3a575872a5b764332c4bacb5a0a"}, + {file = "mypy-1.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:24f3de8b9e7021cd794ad9dfbf2e9fe3f069ff5e28cb57af6f873ffec1cb0425"}, + {file = "mypy-1.6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:856bad61ebc7d21dbc019b719e98303dc6256cec6dcc9ebb0b214b81d6901bd8"}, + {file = "mypy-1.6.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:89513ddfda06b5c8ebd64f026d20a61ef264e89125dc82633f3c34eeb50e7d60"}, + {file = "mypy-1.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:9f8464ed410ada641c29f5de3e6716cbdd4f460b31cf755b2af52f2d5ea79ead"}, + {file = "mypy-1.6.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:971104bcb180e4fed0d7bd85504c9036346ab44b7416c75dd93b5c8c6bb7e28f"}, + {file = "mypy-1.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ab98b8f6fdf669711f3abe83a745f67f50e3cbaea3998b90e8608d2b459fd566"}, + {file = "mypy-1.6.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a69db3018b87b3e6e9dd28970f983ea6c933800c9edf8c503c3135b3274d5ad"}, + {file = "mypy-1.6.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:dccd850a2e3863891871c9e16c54c742dba5470f5120ffed8152956e9e0a5e13"}, + {file = "mypy-1.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:f8598307150b5722854f035d2e70a1ad9cc3c72d392c34fffd8c66d888c90f17"}, + {file = "mypy-1.6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fea451a3125bf0bfe716e5d7ad4b92033c471e4b5b3e154c67525539d14dc15a"}, + {file = "mypy-1.6.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e28d7b221898c401494f3b77db3bac78a03ad0a0fff29a950317d87885c655d2"}, + {file = "mypy-1.6.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4b7a99275a61aa22256bab5839c35fe8a6887781862471df82afb4b445daae6"}, + {file = "mypy-1.6.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7469545380dddce5719e3656b80bdfbb217cfe8dbb1438532d6abc754b828fed"}, + {file = "mypy-1.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:7807a2a61e636af9ca247ba8494031fb060a0a744b9fee7de3a54bed8a753323"}, + {file = "mypy-1.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d2dad072e01764823d4b2f06bc7365bb1d4b6c2f38c4d42fade3c8d45b0b4b67"}, + {file = "mypy-1.6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b19006055dde8a5425baa5f3b57a19fa79df621606540493e5e893500148c72f"}, + {file = "mypy-1.6.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31eba8a7a71f0071f55227a8057468b8d2eb5bf578c8502c7f01abaec8141b2f"}, + {file = "mypy-1.6.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e0db37ac4ebb2fee7702767dfc1b773c7365731c22787cb99f507285014fcaf"}, + {file = "mypy-1.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:c69051274762cccd13498b568ed2430f8d22baa4b179911ad0c1577d336ed849"}, + {file = "mypy-1.6.0-py3-none-any.whl", hash = "sha256:9e1589ca150a51d9d00bb839bfeca2f7a04f32cd62fad87a847bc0818e15d7dc"}, + {file = "mypy-1.6.0.tar.gz", hash = "sha256:4f3d27537abde1be6d5f2c96c29a454da333a2a271ae7d5bc7110e6d4b7beb3f"}, ] [package.dependencies] @@ -903,14 +927,14 @@ attrs = ">=19.2.0" [[package]] name = "packaging" -version = "23.1" +version = "23.2" description = "Core utilities for Python packages" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, - {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, + {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, + {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, ] [[package]] @@ -927,14 +951,14 @@ files = [ [[package]] name = "platformdirs" -version = "3.10.0" +version = "3.11.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "platformdirs-3.10.0-py3-none-any.whl", hash = "sha256:d7c24979f292f916dc9cbf8648319032f551ea8c49a4c9bf2fb556a02070ec1d"}, - {file = "platformdirs-3.10.0.tar.gz", hash = "sha256:b45696dab2d7cc691a3226759c0d3b00c47c8b6e293d96f6436f733303f77f6d"}, + {file = "platformdirs-3.11.0-py3-none-any.whl", hash = "sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e"}, + {file = "platformdirs-3.11.0.tar.gz", hash = "sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3"}, ] [package.extras] @@ -943,14 +967,14 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-co [[package]] name = "pluggy" -version = "1.2.0" +version = "1.3.0" description = "plugin and hook calling mechanisms for python" category = "dev" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pluggy-1.2.0-py3-none-any.whl", hash = "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849"}, - {file = "pluggy-1.2.0.tar.gz", hash = "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3"}, + {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"}, + {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, ] [package.extras] @@ -959,14 +983,14 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "pre-commit" -version = "3.3.3" +version = "3.5.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "pre_commit-3.3.3-py2.py3-none-any.whl", hash = "sha256:10badb65d6a38caff29703362271d7dca483d01da88f9d7e05d0b97171c136cb"}, - {file = "pre_commit-3.3.3.tar.gz", hash = "sha256:a2256f489cd913d575c145132ae196fe335da32d91a8294b7afe6622335dd023"}, + {file = "pre_commit-3.5.0-py2.py3-none-any.whl", hash = "sha256:841dc9aef25daba9a0238cd27984041fa0467b4199fc4852e27950664919f660"}, + {file = "pre_commit-3.5.0.tar.gz", hash = "sha256:5804465c675b659b0862f07907f96295d490822a450c4c40e747d0b1c6ebcb32"}, ] [package.dependencies] @@ -978,72 +1002,81 @@ virtualenv = ">=20.10.0" [[package]] name = "psycopg2-binary" -version = "2.9.7" +version = "2.9.9" description = "psycopg2 - Python-PostgreSQL Database Adapter" category = "main" optional = true -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "psycopg2-binary-2.9.7.tar.gz", hash = "sha256:1b918f64a51ffe19cd2e230b3240ba481330ce1d4b7875ae67305bd1d37b041c"}, - {file = "psycopg2_binary-2.9.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ea5f8ee87f1eddc818fc04649d952c526db4426d26bab16efbe5a0c52b27d6ab"}, - {file = "psycopg2_binary-2.9.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2993ccb2b7e80844d534e55e0f12534c2871952f78e0da33c35e648bf002bbff"}, - {file = "psycopg2_binary-2.9.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dbbc3c5d15ed76b0d9db7753c0db40899136ecfe97d50cbde918f630c5eb857a"}, - {file = "psycopg2_binary-2.9.7-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:692df8763b71d42eb8343f54091368f6f6c9cfc56dc391858cdb3c3ef1e3e584"}, - {file = "psycopg2_binary-2.9.7-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9dcfd5d37e027ec393a303cc0a216be564b96c80ba532f3d1e0d2b5e5e4b1e6e"}, - {file = "psycopg2_binary-2.9.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17cc17a70dfb295a240db7f65b6d8153c3d81efb145d76da1e4a096e9c5c0e63"}, - {file = "psycopg2_binary-2.9.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e5666632ba2b0d9757b38fc17337d84bdf932d38563c5234f5f8c54fd01349c9"}, - {file = "psycopg2_binary-2.9.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7db7b9b701974c96a88997d458b38ccb110eba8f805d4b4f74944aac48639b42"}, - {file = "psycopg2_binary-2.9.7-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c82986635a16fb1fa15cd5436035c88bc65c3d5ced1cfaac7f357ee9e9deddd4"}, - {file = "psycopg2_binary-2.9.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4fe13712357d802080cfccbf8c6266a3121dc0e27e2144819029095ccf708372"}, - {file = "psycopg2_binary-2.9.7-cp310-cp310-win32.whl", hash = "sha256:122641b7fab18ef76b18860dd0c772290566b6fb30cc08e923ad73d17461dc63"}, - {file = "psycopg2_binary-2.9.7-cp310-cp310-win_amd64.whl", hash = "sha256:f8651cf1f144f9ee0fa7d1a1df61a9184ab72962531ca99f077bbdcba3947c58"}, - {file = "psycopg2_binary-2.9.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4ecc15666f16f97709106d87284c136cdc82647e1c3f8392a672616aed3c7151"}, - {file = "psycopg2_binary-2.9.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3fbb1184c7e9d28d67671992970718c05af5f77fc88e26fd7136613c4ece1f89"}, - {file = "psycopg2_binary-2.9.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a7968fd20bd550431837656872c19575b687f3f6f98120046228e451e4064df"}, - {file = "psycopg2_binary-2.9.7-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:094af2e77a1976efd4956a031028774b827029729725e136514aae3cdf49b87b"}, - {file = "psycopg2_binary-2.9.7-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:26484e913d472ecb6b45937ea55ce29c57c662066d222fb0fbdc1fab457f18c5"}, - {file = "psycopg2_binary-2.9.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f309b77a7c716e6ed9891b9b42953c3ff7d533dc548c1e33fddc73d2f5e21f9"}, - {file = "psycopg2_binary-2.9.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6d92e139ca388ccfe8c04aacc163756e55ba4c623c6ba13d5d1595ed97523e4b"}, - {file = "psycopg2_binary-2.9.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:2df562bb2e4e00ee064779902d721223cfa9f8f58e7e52318c97d139cf7f012d"}, - {file = "psycopg2_binary-2.9.7-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:4eec5d36dbcfc076caab61a2114c12094c0b7027d57e9e4387b634e8ab36fd44"}, - {file = "psycopg2_binary-2.9.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1011eeb0c51e5b9ea1016f0f45fa23aca63966a4c0afcf0340ccabe85a9f65bd"}, - {file = "psycopg2_binary-2.9.7-cp311-cp311-win32.whl", hash = "sha256:ded8e15f7550db9e75c60b3d9fcbc7737fea258a0f10032cdb7edc26c2a671fd"}, - {file = "psycopg2_binary-2.9.7-cp311-cp311-win_amd64.whl", hash = "sha256:8a136c8aaf6615653450817a7abe0fc01e4ea720ae41dfb2823eccae4b9062a3"}, - {file = "psycopg2_binary-2.9.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2dec5a75a3a5d42b120e88e6ed3e3b37b46459202bb8e36cd67591b6e5feebc1"}, - {file = "psycopg2_binary-2.9.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc10da7e7df3380426521e8c1ed975d22df678639da2ed0ec3244c3dc2ab54c8"}, - {file = "psycopg2_binary-2.9.7-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee919b676da28f78f91b464fb3e12238bd7474483352a59c8a16c39dfc59f0c5"}, - {file = "psycopg2_binary-2.9.7-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb1c0e682138f9067a58fc3c9a9bf1c83d8e08cfbee380d858e63196466d5c86"}, - {file = "psycopg2_binary-2.9.7-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00d8db270afb76f48a499f7bb8fa70297e66da67288471ca873db88382850bf4"}, - {file = "psycopg2_binary-2.9.7-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:9b0c2b466b2f4d89ccc33784c4ebb1627989bd84a39b79092e560e937a11d4ac"}, - {file = "psycopg2_binary-2.9.7-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:51d1b42d44f4ffb93188f9b39e6d1c82aa758fdb8d9de65e1ddfe7a7d250d7ad"}, - {file = "psycopg2_binary-2.9.7-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:11abdbfc6f7f7dea4a524b5f4117369b0d757725798f1593796be6ece20266cb"}, - {file = "psycopg2_binary-2.9.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:f02f4a72cc3ab2565c6d9720f0343cb840fb2dc01a2e9ecb8bc58ccf95dc5c06"}, - {file = "psycopg2_binary-2.9.7-cp37-cp37m-win32.whl", hash = "sha256:81d5dd2dd9ab78d31a451e357315f201d976c131ca7d43870a0e8063b6b7a1ec"}, - {file = "psycopg2_binary-2.9.7-cp37-cp37m-win_amd64.whl", hash = "sha256:62cb6de84d7767164a87ca97e22e5e0a134856ebcb08f21b621c6125baf61f16"}, - {file = "psycopg2_binary-2.9.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:59f7e9109a59dfa31efa022e94a244736ae401526682de504e87bd11ce870c22"}, - {file = "psycopg2_binary-2.9.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:95a7a747bdc3b010bb6a980f053233e7610276d55f3ca506afff4ad7749ab58a"}, - {file = "psycopg2_binary-2.9.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c721ee464e45ecf609ff8c0a555018764974114f671815a0a7152aedb9f3343"}, - {file = "psycopg2_binary-2.9.7-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f4f37bbc6588d402980ffbd1f3338c871368fb4b1cfa091debe13c68bb3852b3"}, - {file = "psycopg2_binary-2.9.7-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac83ab05e25354dad798401babaa6daa9577462136ba215694865394840e31f8"}, - {file = "psycopg2_binary-2.9.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:024eaeb2a08c9a65cd5f94b31ace1ee3bb3f978cd4d079406aef85169ba01f08"}, - {file = "psycopg2_binary-2.9.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1c31c2606ac500dbd26381145684d87730a2fac9a62ebcfbaa2b119f8d6c19f4"}, - {file = "psycopg2_binary-2.9.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:42a62ef0e5abb55bf6ffb050eb2b0fcd767261fa3faf943a4267539168807522"}, - {file = "psycopg2_binary-2.9.7-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:7952807f95c8eba6a8ccb14e00bf170bb700cafcec3924d565235dffc7dc4ae8"}, - {file = "psycopg2_binary-2.9.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e02bc4f2966475a7393bd0f098e1165d470d3fa816264054359ed4f10f6914ea"}, - {file = "psycopg2_binary-2.9.7-cp38-cp38-win32.whl", hash = "sha256:fdca0511458d26cf39b827a663d7d87db6f32b93efc22442a742035728603d5f"}, - {file = "psycopg2_binary-2.9.7-cp38-cp38-win_amd64.whl", hash = "sha256:d0b16e5bb0ab78583f0ed7ab16378a0f8a89a27256bb5560402749dbe8a164d7"}, - {file = "psycopg2_binary-2.9.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6822c9c63308d650db201ba22fe6648bd6786ca6d14fdaf273b17e15608d0852"}, - {file = "psycopg2_binary-2.9.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8f94cb12150d57ea433e3e02aabd072205648e86f1d5a0a692d60242f7809b15"}, - {file = "psycopg2_binary-2.9.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5ee89587696d808c9a00876065d725d4ae606f5f7853b961cdbc348b0f7c9a1"}, - {file = "psycopg2_binary-2.9.7-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ad5ec10b53cbb57e9a2e77b67e4e4368df56b54d6b00cc86398578f1c635f329"}, - {file = "psycopg2_binary-2.9.7-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:642df77484b2dcaf87d4237792246d8068653f9e0f5c025e2c692fc56b0dda70"}, - {file = "psycopg2_binary-2.9.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6a8b575ac45af1eaccbbcdcf710ab984fd50af048fe130672377f78aaff6fc1"}, - {file = "psycopg2_binary-2.9.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f955aa50d7d5220fcb6e38f69ea126eafecd812d96aeed5d5f3597f33fad43bb"}, - {file = "psycopg2_binary-2.9.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ad26d4eeaa0d722b25814cce97335ecf1b707630258f14ac4d2ed3d1d8415265"}, - {file = "psycopg2_binary-2.9.7-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:ced63c054bdaf0298f62681d5dcae3afe60cbae332390bfb1acf0e23dcd25fc8"}, - {file = "psycopg2_binary-2.9.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2b04da24cbde33292ad34a40db9832a80ad12de26486ffeda883413c9e1b1d5e"}, - {file = "psycopg2_binary-2.9.7-cp39-cp39-win32.whl", hash = "sha256:18f12632ab516c47c1ac4841a78fddea6508a8284c7cf0f292cb1a523f2e2379"}, - {file = "psycopg2_binary-2.9.7-cp39-cp39-win_amd64.whl", hash = "sha256:eb3b8d55924a6058a26db69fb1d3e7e32695ff8b491835ba9f479537e14dcf9f"}, + {file = "psycopg2-binary-2.9.9.tar.gz", hash = "sha256:7f01846810177d829c7692f1f5ada8096762d9172af1b1a28d4ab5b77c923c1c"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c2470da5418b76232f02a2fcd2229537bb2d5a7096674ce61859c3229f2eb202"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c6af2a6d4b7ee9615cbb162b0738f6e1fd1f5c3eda7e5da17861eacf4c717ea7"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75723c3c0fbbf34350b46a3199eb50638ab22a0228f93fb472ef4d9becc2382b"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83791a65b51ad6ee6cf0845634859d69a038ea9b03d7b26e703f94c7e93dbcf9"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0ef4854e82c09e84cc63084a9e4ccd6d9b154f1dbdd283efb92ecd0b5e2b8c84"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed1184ab8f113e8d660ce49a56390ca181f2981066acc27cf637d5c1e10ce46e"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d2997c458c690ec2bc6b0b7ecbafd02b029b7b4283078d3b32a852a7ce3ddd98"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:b58b4710c7f4161b5e9dcbe73bb7c62d65670a87df7bcce9e1faaad43e715245"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0c009475ee389757e6e34611d75f6e4f05f0cf5ebb76c6037508318e1a1e0d7e"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8dbf6d1bc73f1d04ec1734bae3b4fb0ee3cb2a493d35ede9badbeb901fb40f6f"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-win32.whl", hash = "sha256:3f78fd71c4f43a13d342be74ebbc0666fe1f555b8837eb113cb7416856c79682"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-win_amd64.whl", hash = "sha256:876801744b0dee379e4e3c38b76fc89f88834bb15bf92ee07d94acd06ec890a0"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ee825e70b1a209475622f7f7b776785bd68f34af6e7a46e2e42f27b659b5bc26"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1ea665f8ce695bcc37a90ee52de7a7980be5161375d42a0b6c6abedbf0d81f0f"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:143072318f793f53819048fdfe30c321890af0c3ec7cb1dfc9cc87aa88241de2"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c332c8d69fb64979ebf76613c66b985414927a40f8defa16cf1bc028b7b0a7b0"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7fc5a5acafb7d6ccca13bfa8c90f8c51f13d8fb87d95656d3950f0158d3ce53"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:977646e05232579d2e7b9c59e21dbe5261f403a88417f6a6512e70d3f8a046be"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b6356793b84728d9d50ead16ab43c187673831e9d4019013f1402c41b1db9b27"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bc7bb56d04601d443f24094e9e31ae6deec9ccb23581f75343feebaf30423359"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:77853062a2c45be16fd6b8d6de2a99278ee1d985a7bd8b103e97e41c034006d2"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:78151aa3ec21dccd5cdef6c74c3e73386dcdfaf19bced944169697d7ac7482fc"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-win32.whl", hash = "sha256:dc4926288b2a3e9fd7b50dc6a1909a13bbdadfc67d93f3374d984e56f885579d"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-win_amd64.whl", hash = "sha256:b76bedd166805480ab069612119ea636f5ab8f8771e640ae103e05a4aae3e417"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:8532fd6e6e2dc57bcb3bc90b079c60de896d2128c5d9d6f24a63875a95a088cf"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f8544b092a29a6ddd72f3556a9fcf249ec412e10ad28be6a0c0d948924f2212"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d423c8d8a3c82d08fe8af900ad5b613ce3632a1249fd6a223941d0735fce493"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e5afae772c00980525f6d6ecf7cbca55676296b580c0e6abb407f15f3706996"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e6f98446430fdf41bd36d4faa6cb409f5140c1c2cf58ce0bbdaf16af7d3f119"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c77e3d1862452565875eb31bdb45ac62502feabbd53429fdc39a1cc341d681ba"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:cb16c65dcb648d0a43a2521f2f0a2300f40639f6f8c1ecbc662141e4e3e1ee07"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:911dda9c487075abd54e644ccdf5e5c16773470a6a5d3826fda76699410066fb"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:57fede879f08d23c85140a360c6a77709113efd1c993923c59fde17aa27599fe"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2293b001e319ab0d869d660a704942c9e2cce19745262a8aba2115ef41a0a42a"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03ef7df18daf2c4c07e2695e8cfd5ee7f748a1d54d802330985a78d2a5a6dca9"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a602ea5aff39bb9fac6308e9c9d82b9a35c2bf288e184a816002c9fae930b77"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8359bf4791968c5a78c56103702000105501adb557f3cf772b2c207284273984"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:275ff571376626195ab95a746e6a04c7df8ea34638b99fc11160de91f2fef503"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f9b5571d33660d5009a8b3c25dc1db560206e2d2f89d3df1cb32d72c0d117d52"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:420f9bbf47a02616e8554e825208cb947969451978dceb77f95ad09c37791dae"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:4154ad09dac630a0f13f37b583eae260c6aa885d67dfbccb5b02c33f31a6d420"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a148c5d507bb9b4f2030a2025c545fccb0e1ef317393eaba42e7eabd28eb6041"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-win32.whl", hash = "sha256:68fc1f1ba168724771e38bee37d940d2865cb0f562380a1fb1ffb428b75cb692"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-win_amd64.whl", hash = "sha256:281309265596e388ef483250db3640e5f414168c5a67e9c665cafce9492eda2f"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:60989127da422b74a04345096c10d416c2b41bd7bf2a380eb541059e4e999980"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:246b123cc54bb5361588acc54218c8c9fb73068bf227a4a531d8ed56fa3ca7d6"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34eccd14566f8fe14b2b95bb13b11572f7c7d5c36da61caf414d23b91fcc5d94"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18d0ef97766055fec15b5de2c06dd8e7654705ce3e5e5eed3b6651a1d2a9a152"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d3f82c171b4ccd83bbaf35aa05e44e690113bd4f3b7b6cc54d2219b132f3ae55"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ead20f7913a9c1e894aebe47cccf9dc834e1618b7aa96155d2091a626e59c972"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ca49a8119c6cbd77375ae303b0cfd8c11f011abbbd64601167ecca18a87e7cdd"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:323ba25b92454adb36fa425dc5cf6f8f19f78948cbad2e7bc6cdf7b0d7982e59"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:1236ed0952fbd919c100bc839eaa4a39ebc397ed1c08a97fc45fee2a595aa1b3"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:729177eaf0aefca0994ce4cffe96ad3c75e377c7b6f4efa59ebf003b6d398716"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-win32.whl", hash = "sha256:804d99b24ad523a1fe18cc707bf741670332f7c7412e9d49cb5eab67e886b9b5"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-win_amd64.whl", hash = "sha256:a6cdcc3ede532f4a4b96000b6362099591ab4a3e913d70bcbac2b56c872446f7"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:72dffbd8b4194858d0941062a9766f8297e8868e1dd07a7b36212aaa90f49472"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:30dcc86377618a4c8f3b72418df92e77be4254d8f89f14b8e8f57d6d43603c0f"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31a34c508c003a4347d389a9e6fcc2307cc2150eb516462a7a17512130de109e"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:15208be1c50b99203fe88d15695f22a5bed95ab3f84354c494bcb1d08557df67"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1873aade94b74715be2246321c8650cabf5a0d098a95bab81145ffffa4c13876"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a58c98a7e9c021f357348867f537017057c2ed7f77337fd914d0bedb35dace7"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4686818798f9194d03c9129a4d9a702d9e113a89cb03bffe08c6cf799e053291"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ebdc36bea43063116f0486869652cb2ed7032dbc59fbcb4445c4862b5c1ecf7f"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:ca08decd2697fdea0aea364b370b1249d47336aec935f87b8bbfd7da5b2ee9c1"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ac05fb791acf5e1a3e39402641827780fe44d27e72567a000412c648a85ba860"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-win32.whl", hash = "sha256:9dba73be7305b399924709b91682299794887cbbd88e38226ed9f6712eabee90"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-win_amd64.whl", hash = "sha256:f7ae5d65ccfbebdfa761585228eb4d0df3a8b15cfb53bd953e713e09fbb12957"}, ] [[package]] @@ -1060,90 +1093,90 @@ files = [ [[package]] name = "pycryptodomex" -version = "3.18.0" +version = "3.19.0" description = "Cryptographic library for Python" category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ - {file = "pycryptodomex-3.18.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:160a39a708c36fa0b168ab79386dede588e62aec06eb505add870739329aecc6"}, - {file = "pycryptodomex-3.18.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:c2953afebf282a444c51bf4effe751706b4d0d63d7ca2cc51db21f902aa5b84e"}, - {file = "pycryptodomex-3.18.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:ba95abd563b0d1b88401658665a260852a8e6c647026ee6a0a65589287681df8"}, - {file = "pycryptodomex-3.18.0-cp27-cp27m-manylinux2014_aarch64.whl", hash = "sha256:192306cf881fe3467dda0e174a4f47bb3a8bb24b90c9cdfbdc248eec5fc0578c"}, - {file = "pycryptodomex-3.18.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:f9ab5ef0718f6a8716695dea16d83b671b22c45e9c0c78fd807c32c0192e54b5"}, - {file = "pycryptodomex-3.18.0-cp27-cp27m-win32.whl", hash = "sha256:50308fcdbf8345e5ec224a5502b4215178bdb5e95456ead8ab1a69ffd94779cb"}, - {file = "pycryptodomex-3.18.0-cp27-cp27m-win_amd64.whl", hash = "sha256:4d9379c684efea80fdab02a3eb0169372bca7db13f9332cb67483b8dc8b67c37"}, - {file = "pycryptodomex-3.18.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:5594a125dae30d60e94f37797fc67ce3c744522de7992c7c360d02fdb34918f8"}, - {file = "pycryptodomex-3.18.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:8ff129a5a0eb5ff16e45ca4fa70a6051da7f3de303c33b259063c19be0c43d35"}, - {file = "pycryptodomex-3.18.0-cp27-cp27mu-manylinux2014_aarch64.whl", hash = "sha256:3d9314ac785a5b75d5aaf924c5f21d6ca7e8df442e5cf4f0fefad4f6e284d422"}, - {file = "pycryptodomex-3.18.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:f237278836dda412a325e9340ba2e6a84cb0f56b9244781e5b61f10b3905de88"}, - {file = "pycryptodomex-3.18.0-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:ac614363a86cc53d8ba44b6c469831d1555947e69ab3276ae8d6edc219f570f7"}, - {file = "pycryptodomex-3.18.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:302a8f37c224e7b5d72017d462a2be058e28f7be627bdd854066e16722d0fc0c"}, - {file = "pycryptodomex-3.18.0-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:6421d23d6a648e83ba2670a352bcd978542dad86829209f59d17a3f087f4afef"}, - {file = "pycryptodomex-3.18.0-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d84e105787f5e5d36ec6a581ff37a1048d12e638688074b2a00bcf402f9aa1c2"}, - {file = "pycryptodomex-3.18.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6875eb8666f68ddbd39097867325bd22771f595b4e2b0149739b5623c8bf899b"}, - {file = "pycryptodomex-3.18.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:27072a494ce621cc7a9096bbf60ed66826bb94db24b49b7359509e7951033e74"}, - {file = "pycryptodomex-3.18.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:1949e09ea49b09c36d11a951b16ff2a05a0ffe969dda1846e4686ee342fe8646"}, - {file = "pycryptodomex-3.18.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6ed3606832987018615f68e8ed716a7065c09a0fe94afd7c9ca1b6777f0ac6eb"}, - {file = "pycryptodomex-3.18.0-cp35-abi3-win32.whl", hash = "sha256:d56c9ec41258fd3734db9f5e4d2faeabe48644ba9ca23b18e1839b3bdf093222"}, - {file = "pycryptodomex-3.18.0-cp35-abi3-win_amd64.whl", hash = "sha256:e00a4bacb83a2627e8210cb353a2e31f04befc1155db2976e5e239dd66482278"}, - {file = "pycryptodomex-3.18.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:2dc4eab20f4f04a2d00220fdc9258717b82d31913552e766d5f00282c031b70a"}, - {file = "pycryptodomex-3.18.0-pp27-pypy_73-win32.whl", hash = "sha256:75672205148bdea34669173366df005dbd52be05115e919551ee97171083423d"}, - {file = "pycryptodomex-3.18.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bec6c80994d4e7a38312072f89458903b65ec99bed2d65aa4de96d997a53ea7a"}, - {file = "pycryptodomex-3.18.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d35a8ffdc8b05e4b353ba281217c8437f02c57d7233363824e9d794cf753c419"}, - {file = "pycryptodomex-3.18.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76f0a46bee539dae4b3dfe37216f678769349576b0080fdbe431d19a02da42ff"}, - {file = "pycryptodomex-3.18.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:71687eed47df7e965f6e0bf3cadef98f368d5221f0fb89d2132effe1a3e6a194"}, - {file = "pycryptodomex-3.18.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:73d64b32d84cf48d9ec62106aa277dbe99ab5fbfd38c5100bc7bddd3beb569f7"}, - {file = "pycryptodomex-3.18.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbdcce0a226d9205560a5936b05208c709b01d493ed8307792075dedfaaffa5f"}, - {file = "pycryptodomex-3.18.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58fc0aceb9c961b9897facec9da24c6a94c5db04597ec832060f53d4d6a07196"}, - {file = "pycryptodomex-3.18.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:215be2980a6b70704c10796dd7003eb4390e7be138ac6fb8344bf47e71a8d470"}, - {file = "pycryptodomex-3.18.0.tar.gz", hash = "sha256:3e3ecb5fe979e7c1bb0027e518340acf7ee60415d79295e5251d13c68dde576e"}, + {file = "pycryptodomex-3.19.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ff64fd720def623bf64d8776f8d0deada1cc1bf1ec3c1f9d6f5bb5bd098d034f"}, + {file = "pycryptodomex-3.19.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:61056a1fd3254f6f863de94c233b30dd33bc02f8c935b2000269705f1eeeffa4"}, + {file = "pycryptodomex-3.19.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:258c4233a3fe5a6341780306a36c6fb072ef38ce676a6d41eec3e591347919e8"}, + {file = "pycryptodomex-3.19.0-cp27-cp27m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e45bb4635b3c4e0a00ca9df75ef6295838c85c2ac44ad882410cb631ed1eeaa"}, + {file = "pycryptodomex-3.19.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:a12144d785518f6491ad334c75ccdc6ad52ea49230b4237f319dbb7cef26f464"}, + {file = "pycryptodomex-3.19.0-cp27-cp27m-win32.whl", hash = "sha256:1789d89f61f70a4cd5483d4dfa8df7032efab1118f8b9894faae03c967707865"}, + {file = "pycryptodomex-3.19.0-cp27-cp27m-win_amd64.whl", hash = "sha256:eb2fc0ec241bf5e5ef56c8fbec4a2634d631e4c4f616a59b567947a0f35ad83c"}, + {file = "pycryptodomex-3.19.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:c9a68a2f7bd091ccea54ad3be3e9d65eded813e6d79fdf4cc3604e26cdd6384f"}, + {file = "pycryptodomex-3.19.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:8df69e41f7e7015a90b94d1096ec3d8e0182e73449487306709ec27379fff761"}, + {file = "pycryptodomex-3.19.0-cp27-cp27mu-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:917033016ecc23c8933205585a0ab73e20020fdf671b7cd1be788a5c4039840b"}, + {file = "pycryptodomex-3.19.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:e8e5ecbd4da4157889fce8ba49da74764dd86c891410bfd6b24969fa46edda51"}, + {file = "pycryptodomex-3.19.0-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:a77b79852175064c822b047fee7cf5a1f434f06ad075cc9986aa1c19a0c53eb0"}, + {file = "pycryptodomex-3.19.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:5b883e1439ab63af976656446fb4839d566bb096f15fc3c06b5a99cde4927188"}, + {file = "pycryptodomex-3.19.0-cp35-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3866d68e2fc345162b1b9b83ef80686acfe5cec0d134337f3b03950a0a8bf56"}, + {file = "pycryptodomex-3.19.0-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c74eb1f73f788facece7979ce91594dc177e1a9b5d5e3e64697dd58299e5cb4d"}, + {file = "pycryptodomex-3.19.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7cb51096a6a8d400724104db8a7e4f2206041a1f23e58924aa3d8d96bcb48338"}, + {file = "pycryptodomex-3.19.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a588a1cb7781da9d5e1c84affd98c32aff9c89771eac8eaa659d2760666f7139"}, + {file = "pycryptodomex-3.19.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:d4dd3b381ff5a5907a3eb98f5f6d32c64d319a840278ceea1dcfcc65063856f3"}, + {file = "pycryptodomex-3.19.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:263de9a96d2fcbc9f5bd3a279f14ea0d5f072adb68ebd324987576ec25da084d"}, + {file = "pycryptodomex-3.19.0-cp35-abi3-win32.whl", hash = "sha256:67c8eb79ab33d0fbcb56842992298ddb56eb6505a72369c20f60bc1d2b6fb002"}, + {file = "pycryptodomex-3.19.0-cp35-abi3-win_amd64.whl", hash = "sha256:09c9401dc06fb3d94cb1ec23b4ea067a25d1f4c6b7b118ff5631d0b5daaab3cc"}, + {file = "pycryptodomex-3.19.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:edbe083c299835de7e02c8aa0885cb904a75087d35e7bab75ebe5ed336e8c3e2"}, + {file = "pycryptodomex-3.19.0-pp27-pypy_73-win32.whl", hash = "sha256:136b284e9246b4ccf4f752d435c80f2c44fc2321c198505de1d43a95a3453b3c"}, + {file = "pycryptodomex-3.19.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5d73e9fa3fe830e7b6b42afc49d8329b07a049a47d12e0ef9225f2fd220f19b2"}, + {file = "pycryptodomex-3.19.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b2f1982c5bc311f0aab8c293524b861b485d76f7c9ab2c3ac9a25b6f7655975"}, + {file = "pycryptodomex-3.19.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfb040b5dda1dff1e197d2ef71927bd6b8bfcb9793bc4dfe0bb6df1e691eaacb"}, + {file = "pycryptodomex-3.19.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:800a2b05cfb83654df80266692f7092eeefe2a314fa7901dcefab255934faeec"}, + {file = "pycryptodomex-3.19.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c01678aee8ac0c1a461cbc38ad496f953f9efcb1fa19f5637cbeba7544792a53"}, + {file = "pycryptodomex-3.19.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2126bc54beccbede6eade00e647106b4f4c21e5201d2b0a73e9e816a01c50905"}, + {file = "pycryptodomex-3.19.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b801216c48c0886742abf286a9a6b117e248ca144d8ceec1f931ce2dd0c9cb40"}, + {file = "pycryptodomex-3.19.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:50cb18d4dd87571006fd2447ccec85e6cec0136632a550aa29226ba075c80644"}, + {file = "pycryptodomex-3.19.0.tar.gz", hash = "sha256:af83a554b3f077564229865c45af0791be008ac6469ef0098152139e6bd4b5b6"}, ] [[package]] name = "pydantic" -version = "1.10.12" +version = "1.10.13" description = "Data validation and settings management using python type hints" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "pydantic-1.10.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a1fcb59f2f355ec350073af41d927bf83a63b50e640f4dbaa01053a28b7a7718"}, - {file = "pydantic-1.10.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b7ccf02d7eb340b216ec33e53a3a629856afe1c6e0ef91d84a4e6f2fb2ca70fe"}, - {file = "pydantic-1.10.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fb2aa3ab3728d950bcc885a2e9eff6c8fc40bc0b7bb434e555c215491bcf48b"}, - {file = "pydantic-1.10.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:771735dc43cf8383959dc9b90aa281f0b6092321ca98677c5fb6125a6f56d58d"}, - {file = "pydantic-1.10.12-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ca48477862372ac3770969b9d75f1bf66131d386dba79506c46d75e6b48c1e09"}, - {file = "pydantic-1.10.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a5e7add47a5b5a40c49b3036d464e3c7802f8ae0d1e66035ea16aa5b7a3923ed"}, - {file = "pydantic-1.10.12-cp310-cp310-win_amd64.whl", hash = "sha256:e4129b528c6baa99a429f97ce733fff478ec955513630e61b49804b6cf9b224a"}, - {file = "pydantic-1.10.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b0d191db0f92dfcb1dec210ca244fdae5cbe918c6050b342d619c09d31eea0cc"}, - {file = "pydantic-1.10.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:795e34e6cc065f8f498c89b894a3c6da294a936ee71e644e4bd44de048af1405"}, - {file = "pydantic-1.10.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69328e15cfda2c392da4e713443c7dbffa1505bc9d566e71e55abe14c97ddc62"}, - {file = "pydantic-1.10.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2031de0967c279df0d8a1c72b4ffc411ecd06bac607a212892757db7462fc494"}, - {file = "pydantic-1.10.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ba5b2e6fe6ca2b7e013398bc7d7b170e21cce322d266ffcd57cca313e54fb246"}, - {file = "pydantic-1.10.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2a7bac939fa326db1ab741c9d7f44c565a1d1e80908b3797f7f81a4f86bc8d33"}, - {file = "pydantic-1.10.12-cp311-cp311-win_amd64.whl", hash = "sha256:87afda5539d5140cb8ba9e8b8c8865cb5b1463924d38490d73d3ccfd80896b3f"}, - {file = "pydantic-1.10.12-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:549a8e3d81df0a85226963611950b12d2d334f214436a19537b2efed61b7639a"}, - {file = "pydantic-1.10.12-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:598da88dfa127b666852bef6d0d796573a8cf5009ffd62104094a4fe39599565"}, - {file = "pydantic-1.10.12-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba5c4a8552bff16c61882db58544116d021d0b31ee7c66958d14cf386a5b5350"}, - {file = "pydantic-1.10.12-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c79e6a11a07da7374f46970410b41d5e266f7f38f6a17a9c4823db80dadf4303"}, - {file = "pydantic-1.10.12-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab26038b8375581dc832a63c948f261ae0aa21f1d34c1293469f135fa92972a5"}, - {file = "pydantic-1.10.12-cp37-cp37m-win_amd64.whl", hash = "sha256:e0a16d274b588767602b7646fa05af2782576a6cf1022f4ba74cbb4db66f6ca8"}, - {file = "pydantic-1.10.12-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6a9dfa722316f4acf4460afdf5d41d5246a80e249c7ff475c43a3a1e9d75cf62"}, - {file = "pydantic-1.10.12-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a73f489aebd0c2121ed974054cb2759af8a9f747de120acd2c3394cf84176ccb"}, - {file = "pydantic-1.10.12-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b30bcb8cbfccfcf02acb8f1a261143fab622831d9c0989707e0e659f77a18e0"}, - {file = "pydantic-1.10.12-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fcfb5296d7877af406ba1547dfde9943b1256d8928732267e2653c26938cd9c"}, - {file = "pydantic-1.10.12-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2f9a6fab5f82ada41d56b0602606a5506aab165ca54e52bc4545028382ef1c5d"}, - {file = "pydantic-1.10.12-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:dea7adcc33d5d105896401a1f37d56b47d443a2b2605ff8a969a0ed5543f7e33"}, - {file = "pydantic-1.10.12-cp38-cp38-win_amd64.whl", hash = "sha256:1eb2085c13bce1612da8537b2d90f549c8cbb05c67e8f22854e201bde5d98a47"}, - {file = "pydantic-1.10.12-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ef6c96b2baa2100ec91a4b428f80d8f28a3c9e53568219b6c298c1125572ebc6"}, - {file = "pydantic-1.10.12-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6c076be61cd0177a8433c0adcb03475baf4ee91edf5a4e550161ad57fc90f523"}, - {file = "pydantic-1.10.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d5a58feb9a39f481eda4d5ca220aa8b9d4f21a41274760b9bc66bfd72595b86"}, - {file = "pydantic-1.10.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5f805d2d5d0a41633651a73fa4ecdd0b3d7a49de4ec3fadf062fe16501ddbf1"}, - {file = "pydantic-1.10.12-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:1289c180abd4bd4555bb927c42ee42abc3aee02b0fb2d1223fb7c6e5bef87dbe"}, - {file = "pydantic-1.10.12-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5d1197e462e0364906cbc19681605cb7c036f2475c899b6f296104ad42b9f5fb"}, - {file = "pydantic-1.10.12-cp39-cp39-win_amd64.whl", hash = "sha256:fdbdd1d630195689f325c9ef1a12900524dceb503b00a987663ff4f58669b93d"}, - {file = "pydantic-1.10.12-py3-none-any.whl", hash = "sha256:b749a43aa51e32839c9d71dc67eb1e4221bb04af1033a32e3923d46f9effa942"}, - {file = "pydantic-1.10.12.tar.gz", hash = "sha256:0fe8a415cea8f340e7a9af9c54fc71a649b43e8ca3cc732986116b3cb135d303"}, + {file = "pydantic-1.10.13-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:efff03cc7a4f29d9009d1c96ceb1e7a70a65cfe86e89d34e4a5f2ab1e5693737"}, + {file = "pydantic-1.10.13-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3ecea2b9d80e5333303eeb77e180b90e95eea8f765d08c3d278cd56b00345d01"}, + {file = "pydantic-1.10.13-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1740068fd8e2ef6eb27a20e5651df000978edce6da6803c2bef0bc74540f9548"}, + {file = "pydantic-1.10.13-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84bafe2e60b5e78bc64a2941b4c071a4b7404c5c907f5f5a99b0139781e69ed8"}, + {file = "pydantic-1.10.13-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bc0898c12f8e9c97f6cd44c0ed70d55749eaf783716896960b4ecce2edfd2d69"}, + {file = "pydantic-1.10.13-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:654db58ae399fe6434e55325a2c3e959836bd17a6f6a0b6ca8107ea0571d2e17"}, + {file = "pydantic-1.10.13-cp310-cp310-win_amd64.whl", hash = "sha256:75ac15385a3534d887a99c713aa3da88a30fbd6204a5cd0dc4dab3d770b9bd2f"}, + {file = "pydantic-1.10.13-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c553f6a156deb868ba38a23cf0df886c63492e9257f60a79c0fd8e7173537653"}, + {file = "pydantic-1.10.13-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5e08865bc6464df8c7d61439ef4439829e3ab62ab1669cddea8dd00cd74b9ffe"}, + {file = "pydantic-1.10.13-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e31647d85a2013d926ce60b84f9dd5300d44535a9941fe825dc349ae1f760df9"}, + {file = "pydantic-1.10.13-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:210ce042e8f6f7c01168b2d84d4c9eb2b009fe7bf572c2266e235edf14bacd80"}, + {file = "pydantic-1.10.13-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:8ae5dd6b721459bfa30805f4c25880e0dd78fc5b5879f9f7a692196ddcb5a580"}, + {file = "pydantic-1.10.13-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f8e81fc5fb17dae698f52bdd1c4f18b6ca674d7068242b2aff075f588301bbb0"}, + {file = "pydantic-1.10.13-cp311-cp311-win_amd64.whl", hash = "sha256:61d9dce220447fb74f45e73d7ff3b530e25db30192ad8d425166d43c5deb6df0"}, + {file = "pydantic-1.10.13-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4b03e42ec20286f052490423682016fd80fda830d8e4119f8ab13ec7464c0132"}, + {file = "pydantic-1.10.13-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f59ef915cac80275245824e9d771ee939133be38215555e9dc90c6cb148aaeb5"}, + {file = "pydantic-1.10.13-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a1f9f747851338933942db7af7b6ee8268568ef2ed86c4185c6ef4402e80ba8"}, + {file = "pydantic-1.10.13-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:97cce3ae7341f7620a0ba5ef6cf043975cd9d2b81f3aa5f4ea37928269bc1b87"}, + {file = "pydantic-1.10.13-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:854223752ba81e3abf663d685f105c64150873cc6f5d0c01d3e3220bcff7d36f"}, + {file = "pydantic-1.10.13-cp37-cp37m-win_amd64.whl", hash = "sha256:b97c1fac8c49be29486df85968682b0afa77e1b809aff74b83081cc115e52f33"}, + {file = "pydantic-1.10.13-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c958d053453a1c4b1c2062b05cd42d9d5c8eb67537b8d5a7e3c3032943ecd261"}, + {file = "pydantic-1.10.13-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4c5370a7edaac06daee3af1c8b1192e305bc102abcbf2a92374b5bc793818599"}, + {file = "pydantic-1.10.13-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d6f6e7305244bddb4414ba7094ce910560c907bdfa3501e9db1a7fd7eaea127"}, + {file = "pydantic-1.10.13-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d3a3c792a58e1622667a2837512099eac62490cdfd63bd407993aaf200a4cf1f"}, + {file = "pydantic-1.10.13-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c636925f38b8db208e09d344c7aa4f29a86bb9947495dd6b6d376ad10334fb78"}, + {file = "pydantic-1.10.13-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:678bcf5591b63cc917100dc50ab6caebe597ac67e8c9ccb75e698f66038ea953"}, + {file = "pydantic-1.10.13-cp38-cp38-win_amd64.whl", hash = "sha256:6cf25c1a65c27923a17b3da28a0bdb99f62ee04230c931d83e888012851f4e7f"}, + {file = "pydantic-1.10.13-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8ef467901d7a41fa0ca6db9ae3ec0021e3f657ce2c208e98cd511f3161c762c6"}, + {file = "pydantic-1.10.13-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:968ac42970f57b8344ee08837b62f6ee6f53c33f603547a55571c954a4225691"}, + {file = "pydantic-1.10.13-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9849f031cf8a2f0a928fe885e5a04b08006d6d41876b8bbd2fc68a18f9f2e3fd"}, + {file = "pydantic-1.10.13-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:56e3ff861c3b9c6857579de282ce8baabf443f42ffba355bf070770ed63e11e1"}, + {file = "pydantic-1.10.13-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f00790179497767aae6bcdc36355792c79e7bbb20b145ff449700eb076c5f96"}, + {file = "pydantic-1.10.13-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:75b297827b59bc229cac1a23a2f7a4ac0031068e5be0ce385be1462e7e17a35d"}, + {file = "pydantic-1.10.13-cp39-cp39-win_amd64.whl", hash = "sha256:e70ca129d2053fb8b728ee7d1af8e553a928d7e301a311094b8a0501adc8763d"}, + {file = "pydantic-1.10.13-py3-none-any.whl", hash = "sha256:b87326822e71bd5f313e7d3bfdc77ac3247035ac10b0c0618bd99dcf95b1e687"}, + {file = "pydantic-1.10.13.tar.gz", hash = "sha256:32c8b48dcd3b2ac4e78b0ba4af3a2c2eb6048cb75202f0ea7b34feb740efc340"}, ] [package.dependencies] @@ -1168,14 +1201,14 @@ files = [ [[package]] name = "pytest" -version = "7.4.0" +version = "7.4.2" description = "pytest: simple powerful testing with Python" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-7.4.0-py3-none-any.whl", hash = "sha256:78bf16451a2eb8c7a2ea98e32dc119fd2aa758f1d5d66dbf0a59d69a3969df32"}, - {file = "pytest-7.4.0.tar.gz", hash = "sha256:b4bf8c45bd59934ed84001ad51e11b4ee40d40a1229d2c79f9c592b0a3f6bd8a"}, + {file = "pytest-7.4.2-py3-none-any.whl", hash = "sha256:1d881c6124e08ff0a1bb75ba3ec0bfd8b5354a01c194ddd5a0a870a48d99b002"}, + {file = "pytest-7.4.2.tar.gz", hash = "sha256:a766259cfab564a2ad52cb1aae1b881a75c3eb7e34ca3779697c23ed47c47069"}, ] [package.dependencies] @@ -1397,20 +1430,20 @@ cffi = ">=1.3.0" [[package]] name = "setuptools" -version = "68.1.2" +version = "68.2.2" description = "Easily download, build, install, upgrade, and uninstall Python packages" category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-68.1.2-py3-none-any.whl", hash = "sha256:3d8083eed2d13afc9426f227b24fd1659489ec107c0e86cec2ffdde5c92e790b"}, - {file = "setuptools-68.1.2.tar.gz", hash = "sha256:3d4dfa6d95f1b101d695a6160a7626e15583af71a5f52176efa5d39a054d475d"}, + {file = "setuptools-68.2.2-py3-none-any.whl", hash = "sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a"}, + {file = "setuptools-68.2.2.tar.gz", hash = "sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5,<=7.1.2)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "six" @@ -1547,53 +1580,41 @@ files = [ [[package]] name = "types-requests" -version = "2.31.0.2" +version = "2.31.0.9" description = "Typing stubs for requests" category = "dev" optional = false -python-versions = "*" +python-versions = ">=3.7" files = [ - {file = "types-requests-2.31.0.2.tar.gz", hash = "sha256:6aa3f7faf0ea52d728bb18c0a0d1522d9bfd8c72d26ff6f61bfc3d06a411cf40"}, - {file = "types_requests-2.31.0.2-py3-none-any.whl", hash = "sha256:56d181c85b5925cbc59f4489a57e72a8b2166f18273fd8ba7b6fe0c0b986f12a"}, + {file = "types-requests-2.31.0.9.tar.gz", hash = "sha256:3bb11188795cc3aa39f9635032044ee771009370fb31c3a06ae952b267b6fcd7"}, + {file = "types_requests-2.31.0.9-py3-none-any.whl", hash = "sha256:140e323da742a0cd0ff0a5a83669da9ffcebfaeb855d367186b2ec3985ba2742"}, ] [package.dependencies] -types-urllib3 = "*" - -[[package]] -name = "types-urllib3" -version = "1.26.25.14" -description = "Typing stubs for urllib3" -category = "dev" -optional = false -python-versions = "*" -files = [ - {file = "types-urllib3-1.26.25.14.tar.gz", hash = "sha256:229b7f577c951b8c1b92c1bc2b2fdb0b49847bd2af6d1cc2a2e3dd340f3bda8f"}, - {file = "types_urllib3-1.26.25.14-py3-none-any.whl", hash = "sha256:9683bbb7fb72e32bfe9d2be6e04875fbe1b3eeec3cbb4ea231435aa7fd6b4f0e"}, -] +urllib3 = ">=2" [[package]] name = "typing-extensions" -version = "4.7.1" -description = "Backported and Experimental Type Hints for Python 3.7+" +version = "4.8.0" +description = "Backported and Experimental Type Hints for Python 3.8+" category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, - {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, + {file = "typing_extensions-4.8.0-py3-none-any.whl", hash = "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0"}, + {file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"}, ] [[package]] name = "urllib3" -version = "2.0.4" +version = "2.0.6" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "urllib3-2.0.4-py3-none-any.whl", hash = "sha256:de7df1803967d2c2a98e4b11bb7d6bd9210474c46e8a0401514e3a42a75ebde4"}, - {file = "urllib3-2.0.4.tar.gz", hash = "sha256:8d22f86aae8ef5e410d4f539fde9ce6b2113a001bb4d189e0aed70642d602b11"}, + {file = "urllib3-2.0.6-py3-none-any.whl", hash = "sha256:7a7c7003b000adf9e7ca2a377c9688bbc54ed41b985789ed576570342a375cd2"}, + {file = "urllib3-2.0.6.tar.gz", hash = "sha256:b19e1a85d206b56d7df1d5e683df4a7725252a964e3993648dd0fb5a1c157564"}, ] [package.extras] @@ -1604,33 +1625,34 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "uvicorn" -version = "0.18.3" +version = "0.23.2" description = "The lightning-fast ASGI server." category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "uvicorn-0.18.3-py3-none-any.whl", hash = "sha256:0abd429ebb41e604ed8d2be6c60530de3408f250e8d2d84967d85ba9e86fe3af"}, - {file = "uvicorn-0.18.3.tar.gz", hash = "sha256:9a66e7c42a2a95222f76ec24a4b754c158261c4696e683b9dadc72b590e0311b"}, + {file = "uvicorn-0.23.2-py3-none-any.whl", hash = "sha256:1f9be6558f01239d4fdf22ef8126c39cb1ad0addf76c40e760549d2c2f43ab53"}, + {file = "uvicorn-0.23.2.tar.gz", hash = "sha256:4d3cc12d7727ba72b64d12d3cc7743124074c0a69f7b201512fc50c3e3f1569a"}, ] [package.dependencies] click = ">=7.0" h11 = ">=0.8" +typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""} [package.extras] -standard = ["colorama (>=0.4)", "httptools (>=0.4.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.0)"] +standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] [[package]] name = "virtualenv" -version = "20.24.3" +version = "20.24.5" description = "Virtual Python Environment builder" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "virtualenv-20.24.3-py3-none-any.whl", hash = "sha256:95a6e9398b4967fbcb5fef2acec5efaf9aa4972049d9ae41f95e0972a683fd02"}, - {file = "virtualenv-20.24.3.tar.gz", hash = "sha256:e5c3b4ce817b0b328af041506a2a299418c98747c4b1e68cb7527e74ced23efc"}, + {file = "virtualenv-20.24.5-py3-none-any.whl", hash = "sha256:b80039f280f4919c77b30f1c23294ae357c4c8701042086e3fc005963e4e537b"}, + {file = "virtualenv-20.24.5.tar.gz", hash = "sha256:e8361967f6da6fbdf1426483bfe9fca8287c242ac0bc30429905721cefbff752"}, ] [package.dependencies] @@ -1639,19 +1661,19 @@ filelock = ">=3.12.2,<4" platformdirs = ">=3.9.1,<4" [package.extras] -docs = ["furo (>=2023.5.20)", "proselint (>=0.13)", "sphinx (>=7.0.1)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] [[package]] name = "websocket-client" -version = "1.6.2" +version = "1.6.4" description = "WebSocket client for Python with low level API options" category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "websocket-client-1.6.2.tar.gz", hash = "sha256:53e95c826bf800c4c465f50093a8c4ff091c7327023b10bfaff40cf1ef170eaa"}, - {file = "websocket_client-1.6.2-py3-none-any.whl", hash = "sha256:ce54f419dfae71f4bdba69ebe65bf7f0a93fe71bc009ad3a010aacc3eebad537"}, + {file = "websocket-client-1.6.4.tar.gz", hash = "sha256:b3324019b3c28572086c4a319f91d1dcd44e6e11cd340232978c684a7650d0df"}, + {file = "websocket_client-1.6.4-py3-none-any.whl", hash = "sha256:084072e0a7f5f347ef2ac3d8698a5e0b4ffbfcab607628cadabc650fc9a83a24"}, ] [package.extras] @@ -1691,18 +1713,18 @@ dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"] [[package]] name = "zipp" -version = "3.16.2" +version = "3.17.0" description = "Backport of pathlib-compatible object wrapper for zip files" category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "zipp-3.16.2-py3-none-any.whl", hash = "sha256:679e51dd4403591b2d6838a48de3d283f3d188412a9782faadf845f298736ba0"}, - {file = "zipp-3.16.2.tar.gz", hash = "sha256:ebc15946aa78bd63458992fc81ec3b6f7b1e92d51c35e6de1c3804e73b799147"}, + {file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"}, + {file = "zipp-3.17.0.tar.gz", hash = "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] [extras] @@ -1711,4 +1733,4 @@ pgsql = ["psycopg2-binary"] [metadata] lock-version = "2.0" python-versions = "^3.8.1" -content-hash = "c8a62986bb458c849aabdd3e2f1e1534e4af6093863177fc27630fc9daa5410a" +content-hash = "f6d0c2b084dff91f046d7d6b35fe3dde778b2c94b24de4d5a0e9eec206610154" diff --git a/pyproject.toml b/pyproject.toml index f442501c..cec119b5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,9 +12,9 @@ SQLAlchemy = "^1.3.24" click = "^8.1.7" pydantic = "^1.10.2" bech32 = "^1.2.0" -fastapi = "^0.101.1" +fastapi = "0.103.0" environs = "^9.5.0" -uvicorn = "^0.18.3" +uvicorn = "0.23.2" loguru = "^0.7.0" ecdsa = "^0.18.0" bitstring = "^3.1.9" @@ -29,9 +29,10 @@ setuptools = "^68.1.2" wheel = "^0.41.1" importlib-metadata = "^6.8.0" psycopg2-binary = { version = "^2.9.7", optional = true } -httpx = "^0.24.1" +httpx = "0.25.0" bip32 = "^3.4" mnemonic = "^0.20" +bolt11 = "^2.0.5" [tool.poetry.extras] pgsql = ["psycopg2-binary"] diff --git a/tests/conftest.py b/tests/conftest.py index 2a7ac40b..02751323 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -14,6 +14,7 @@ from cashu.core.settings import settings from cashu.lightning.fake import FakeWallet from cashu.mint import migrations as migrations_mint +from cashu.mint.crud import LedgerCrud from cashu.mint.ledger import Ledger SERVER_PORT = 3337 @@ -64,6 +65,7 @@ async def start_mint_init(ledger: Ledger): seed=settings.mint_private_key, derivation_path=settings.mint_derivation_path, lightning=FakeWallet(), + crud=LedgerCrud(), ) await start_mint_init(ledger) yield ledger diff --git a/tests/test_mint.py b/tests/test_mint.py index 07908e86..404f42ab 100644 --- a/tests/test_mint.py +++ b/tests/test_mint.py @@ -66,14 +66,14 @@ async def test_get_keyset(ledger: Ledger): @pytest.mark.asyncio async def test_mint(ledger: Ledger): - invoice, payment_hash = await ledger.request_mint(8) + invoice, id = await ledger.request_mint(8) blinded_messages_mock = [ BlindedMessage( amount=8, B_="02634a2c2b34bec9e8a4aba4361f6bf202d7fa2365379b0840afe249a7a9d71239", ) ] - promises = await ledger.mint(blinded_messages_mock, hash=payment_hash) + promises = await ledger.mint(blinded_messages_mock, id=id) assert len(promises) assert promises[0].amount == 8 assert ( @@ -84,7 +84,7 @@ async def test_mint(ledger: Ledger): @pytest.mark.asyncio async def test_mint_invalid_blinded_message(ledger: Ledger): - invoice, payment_hash = await ledger.request_mint(8) + invoice, id = await ledger.request_mint(8) blinded_messages_mock_invalid_key = [ BlindedMessage( amount=8, @@ -92,7 +92,7 @@ async def test_mint_invalid_blinded_message(ledger: Ledger): ) ] await assert_err( - ledger.mint(blinded_messages_mock_invalid_key, hash=payment_hash), + ledger.mint(blinded_messages_mock_invalid_key, id=id), "invalid public key", ) diff --git a/tests/test_mint_operations.py b/tests/test_mint_operations.py index 916d8f56..35e8c479 100644 --- a/tests/test_mint_operations.py +++ b/tests/test_mint_operations.py @@ -23,23 +23,25 @@ async def wallet1(mint): async def test_melt(wallet1: Wallet, ledger: Ledger): # mint twice so we have enough to pay the second invoice back invoice = await wallet1.request_mint(64) - await wallet1.mint(64, hash=invoice.hash) + await wallet1.mint(64, id=invoice.id) invoice = await wallet1.request_mint(64) - await wallet1.mint(64, hash=invoice.hash) + await wallet1.mint(64, id=invoice.id) assert wallet1.balance == 128 - total_amount, fee_reserve_sat = await wallet1.get_pay_amount_with_fees(invoice.pr) - mint_fees = await ledger.get_melt_fees(invoice.pr) + total_amount, fee_reserve_sat = await wallet1.get_pay_amount_with_fees( + invoice.bolt11 + ) + mint_fees = await ledger.get_melt_fees(invoice.bolt11) assert mint_fees == fee_reserve_sat keep_proofs, send_proofs = await wallet1.split_to_send(wallet1.proofs, total_amount) - await ledger.melt(send_proofs, invoice.pr, outputs=None) + await ledger.melt(send_proofs, invoice.bolt11, outputs=None) @pytest.mark.asyncio async def test_split(wallet1: Wallet, ledger: Ledger): invoice = await wallet1.request_mint(64) - await wallet1.mint(64, hash=invoice.hash) + await wallet1.mint(64, id=invoice.id) keep_proofs, send_proofs = await wallet1.split_to_send(wallet1.proofs, 10) secrets, rs, derivation_paths = await wallet1.generate_n_secrets(len(send_proofs)) @@ -55,7 +57,7 @@ async def test_split(wallet1: Wallet, ledger: Ledger): @pytest.mark.asyncio async def test_check_proof_state(wallet1: Wallet, ledger: Ledger): invoice = await wallet1.request_mint(64) - await wallet1.mint(64, hash=invoice.hash) + await wallet1.mint(64, id=invoice.id) keep_proofs, send_proofs = await wallet1.split_to_send(wallet1.proofs, 10) diff --git a/tests/test_wallet.py b/tests/test_wallet.py index 0bf01868..a869a85a 100644 --- a/tests/test_wallet.py +++ b/tests/test_wallet.py @@ -9,6 +9,7 @@ from cashu.core.errors import CashuError, KeysetNotFoundError from cashu.core.helpers import sum_proofs from cashu.core.settings import settings +from cashu.wallet.crud import get_lightning_invoice, get_proofs from cashu.wallet.wallet import Wallet from cashu.wallet.wallet import Wallet as Wallet1 from cashu.wallet.wallet import Wallet as Wallet2 @@ -137,16 +138,28 @@ async def test_get_keyset_ids(wallet1: Wallet): @pytest.mark.asyncio async def test_mint(wallet1: Wallet): invoice = await wallet1.request_mint(64) - await wallet1.mint(64, hash=invoice.hash) + await wallet1.mint(64, id=invoice.id) assert wallet1.balance == 64 + # verify that proofs in proofs_used db have the same mint_id as the invoice in the db + assert invoice.payment_hash + invoice_db = await get_lightning_invoice( + db=wallet1.db, payment_hash=invoice.payment_hash, out=False + ) + assert invoice_db + proofs_minted = await get_proofs( + db=wallet1.db, mint_id=invoice_db.id, table="proofs" + ) + assert len(proofs_minted) == 1 + assert all([p.mint_id == invoice.id for p in proofs_minted]) + @pytest.mark.asyncio async def test_mint_amounts(wallet1: Wallet): """Mint predefined amounts""" invoice = await wallet1.request_mint(64) amts = [1, 1, 1, 2, 2, 4, 16] - await wallet1.mint(amount=sum(amts), split=amts, hash=invoice.hash) + await wallet1.mint(amount=sum(amts), split=amts, id=invoice.id) assert wallet1.balance == 27 assert wallet1.proof_amounts == amts @@ -174,7 +187,7 @@ async def test_mint_amounts_wrong_order(wallet1: Wallet): @pytest.mark.asyncio async def test_split(wallet1: Wallet): invoice = await wallet1.request_mint(64) - await wallet1.mint(64, hash=invoice.hash) + await wallet1.mint(64, id=invoice.id) assert wallet1.balance == 64 p1, p2 = await wallet1.split(wallet1.proofs, 20) assert wallet1.balance == 64 @@ -189,7 +202,7 @@ async def test_split(wallet1: Wallet): @pytest.mark.asyncio async def test_split_to_send(wallet1: Wallet): invoice = await wallet1.request_mint(64) - await wallet1.mint(64, hash=invoice.hash) + await wallet1.mint(64, id=invoice.id) keep_proofs, spendable_proofs = await wallet1.split_to_send( wallet1.proofs, 32, set_reserved=True ) @@ -204,7 +217,7 @@ async def test_split_to_send(wallet1: Wallet): @pytest.mark.asyncio async def test_split_more_than_balance(wallet1: Wallet): invoice = await wallet1.request_mint(64) - await wallet1.mint(64, hash=invoice.hash) + await wallet1.mint(64, id=invoice.id) await assert_err( wallet1.split(wallet1.proofs, 128), # "Mint Error: inputs do not have same amount as outputs", @@ -217,24 +230,45 @@ async def test_split_more_than_balance(wallet1: Wallet): async def test_melt(wallet1: Wallet): # mint twice so we have enough to pay the second invoice back invoice = await wallet1.request_mint(64) - await wallet1.mint(64, hash=invoice.hash) + await wallet1.mint(64, id=invoice.id) invoice = await wallet1.request_mint(64) - await wallet1.mint(64, hash=invoice.hash) + await wallet1.mint(64, id=invoice.id) assert wallet1.balance == 128 - total_amount, fee_reserve_sat = await wallet1.get_pay_amount_with_fees(invoice.pr) + + total_amount, fee_reserve_sat = await wallet1.get_pay_amount_with_fees( + invoice.bolt11 + ) + assert total_amount == 66 + + assert fee_reserve_sat == 2 _, send_proofs = await wallet1.split_to_send(wallet1.proofs, total_amount) await wallet1.pay_lightning( - send_proofs, invoice=invoice.pr, fee_reserve_sat=fee_reserve_sat + send_proofs, invoice=invoice.bolt11, fee_reserve_sat=fee_reserve_sat ) + + # verify that proofs in proofs_used db have the same melt_id as the invoice in the db + assert invoice.payment_hash + invoice_db = await get_lightning_invoice( + db=wallet1.db, payment_hash=invoice.payment_hash, out=True + ) + assert invoice_db + proofs_used = await get_proofs( + db=wallet1.db, melt_id=invoice_db.id, table="proofs_used" + ) + + assert len(proofs_used) == len(send_proofs) + assert all([p.melt_id == invoice_db.id for p in proofs_used]) + # the payment was without fees so we need to remove it from the total amount assert wallet1.balance == 128 - (total_amount - fee_reserve_sat) + assert wallet1.balance == 64 @pytest.mark.asyncio async def test_split_to_send_more_than_balance(wallet1: Wallet): invoice = await wallet1.request_mint(64) - await wallet1.mint(64, hash=invoice.hash) + await wallet1.mint(64, id=invoice.id) await assert_err( wallet1.split_to_send(wallet1.proofs, 128, set_reserved=True), "balance too low.", @@ -246,7 +280,7 @@ async def test_split_to_send_more_than_balance(wallet1: Wallet): @pytest.mark.asyncio async def test_double_spend(wallet1: Wallet): invoice = await wallet1.request_mint(64) - doublespend = await wallet1.mint(64, hash=invoice.hash) + doublespend = await wallet1.mint(64, id=invoice.id) await wallet1.split(wallet1.proofs, 20) await assert_err( wallet1.split(doublespend, 20), @@ -259,7 +293,7 @@ async def test_double_spend(wallet1: Wallet): @pytest.mark.asyncio async def test_duplicate_proofs_double_spent(wallet1: Wallet): invoice = await wallet1.request_mint(64) - doublespend = await wallet1.mint(64, hash=invoice.hash) + doublespend = await wallet1.mint(64, id=invoice.id) await assert_err( wallet1.split(wallet1.proofs + doublespend, 20), "Mint Error: proofs already pending.", @@ -271,7 +305,7 @@ async def test_duplicate_proofs_double_spent(wallet1: Wallet): @pytest.mark.asyncio async def test_send_and_redeem(wallet1: Wallet, wallet2: Wallet): invoice = await wallet1.request_mint(64) - await wallet1.mint(64, hash=invoice.hash) + await wallet1.mint(64, id=invoice.id) _, spendable_proofs = await wallet1.split_to_send( wallet1.proofs, 32, set_reserved=True ) @@ -289,7 +323,7 @@ async def test_send_and_redeem(wallet1: Wallet, wallet2: Wallet): async def test_invalidate_unspent_proofs(wallet1: Wallet): """Try to invalidate proofs that have not been spent yet. Should not work!""" invoice = await wallet1.request_mint(64) - await wallet1.mint(64, hash=invoice.hash) + await wallet1.mint(64, id=invoice.id) await wallet1.invalidate(wallet1.proofs) assert wallet1.balance == 64 @@ -298,7 +332,7 @@ async def test_invalidate_unspent_proofs(wallet1: Wallet): async def test_invalidate_unspent_proofs_without_checking(wallet1: Wallet): """Try to invalidate proofs that have not been spent yet but force no check.""" invoice = await wallet1.request_mint(64) - await wallet1.mint(64, hash=invoice.hash) + await wallet1.mint(64, id=invoice.id) await wallet1.invalidate(wallet1.proofs, check_spendable=False) assert wallet1.balance == 0 @@ -306,7 +340,7 @@ async def test_invalidate_unspent_proofs_without_checking(wallet1: Wallet): @pytest.mark.asyncio async def test_split_invalid_amount(wallet1: Wallet): invoice = await wallet1.request_mint(64) - await wallet1.mint(64, hash=invoice.hash) + await wallet1.mint(64, id=invoice.id) await assert_err( wallet1.split(wallet1.proofs, -1), "amount must be positive.", @@ -316,7 +350,7 @@ async def test_split_invalid_amount(wallet1: Wallet): @pytest.mark.asyncio async def test_token_state(wallet1: Wallet): invoice = await wallet1.request_mint(64) - await wallet1.mint(64, hash=invoice.hash) + await wallet1.mint(64, id=invoice.id) assert wallet1.balance == 64 resp = await wallet1.check_proof_state(wallet1.proofs) assert resp.dict()["spendable"] diff --git a/tests/test_wallet_api.py b/tests/test_wallet_api.py index 799f834c..d07fa388 100644 --- a/tests/test_wallet_api.py +++ b/tests/test_wallet_api.py @@ -1,8 +1,10 @@ +import asyncio + import pytest import pytest_asyncio from fastapi.testclient import TestClient -from cashu.core.settings import settings +from cashu.lightning.base import InvoiceResponse, PaymentStatus from cashu.wallet.api.app import app from cashu.wallet.wallet import Wallet from tests.conftest import SERVER_ENDPOINT @@ -23,25 +25,19 @@ async def wallet(mint): @pytest.mark.asyncio async def test_invoice(wallet: Wallet): with TestClient(app) as client: - response = client.post("/invoice?amount=100") - assert response.status_code == 200 - if settings.lightning: - assert response.json()["invoice"] - else: - assert response.json()["amount"] - - -@pytest.mark.asyncio -async def test_invoice_with_split(wallet: Wallet): - with TestClient(app) as client: - response = client.post("/invoice?amount=10&split=1") + response = client.post("/lightning/create_invoice?amount=100") assert response.status_code == 200 - if settings.lightning: - assert response.json()["invoice"] - else: - assert response.json()["amount"] - # await wallet.load_proofs(reload=True) - # assert wallet.proof_amounts.count(1) >= 10 + invoice_response = InvoiceResponse.parse_obj(response.json()) + state = PaymentStatus(paid=False) + while not state.paid: + print("checking invoice state") + response2 = client.get( + f"/lightning/invoice_state?payment_hash={invoice_response.checking_id}" + ) + state = PaymentStatus.parse_obj(response2.json()) + await asyncio.sleep(0.1) + print("state:", state) + print("paid") @pytest.mark.asyncio @@ -49,7 +45,7 @@ async def test_balance(): with TestClient(app) as client: response = client.get("/balance") assert response.status_code == 200 - assert response.json()["balance"] + assert "balance" in response.json() assert response.json()["keysets"] assert response.json()["mints"] @@ -89,7 +85,7 @@ async def test_receive_all(wallet: Wallet): with TestClient(app) as client: response = client.post("/receive?all=true") assert response.status_code == 200 - assert response.json()["initial_balance"] == 0 + assert response.json()["initial_balance"] assert response.json()["balance"] @@ -100,24 +96,21 @@ async def test_burn_all(wallet: Wallet): assert response.status_code == 200 response = client.post("/burn?all=true") assert response.status_code == 200 - assert response.json()["balance"] == 0 + assert response.json()["balance"] @pytest.mark.asyncio async def test_pay(): with TestClient(app) as client: invoice = ( - "lnbc100n1pjzp22cpp58xvjxvagzywky9xz3vurue822aaax" - "735hzc5pj5fg307y58v5znqdq4vdshx6r4ypjx2ur0wd5hgl" - "h6ahauv24wdmac4zk478pmwfzd7sdvm8tje3dmfue3lc2g4l" - "9g40a073h39748uez9p8mxws5vqwjmkqr4wl5l7n4dlhj6z6" - "va963cqvufrs4" + "lnbc100n1pjjcqzfdq4gdshx6r4ypjx2ur0wd5hgpp58xvj8yn00d5" + "7uhshwzcwgy9uj3vwf5y2lr5fjf78s4w9l4vhr6xssp5stezsyty9r" + "hv3lat69g4mhqxqun56jyehhkq3y8zufh83xyfkmmq4usaqwrt5q4f" + "adm44g6crckp0hzvuyv9sja7t65hxj0ucf9y46qstkay7gfnwhuxgr" + "krf7djs38rml39l8wpn5ug9shp3n55quxhdecqfwxg23" ) - response = client.post(f"/pay?invoice={invoice}") - if not settings.lightning: - assert response.status_code == 400 - else: - assert response.status_code == 200 + response = client.post(f"/lightning/pay_invoice?bolt11={invoice}") + assert response.status_code == 200 @pytest.mark.asyncio @@ -159,21 +152,31 @@ async def test_info(): @pytest.mark.asyncio async def test_flow(wallet: Wallet): with TestClient(app) as client: - if not settings.lightning: - response = client.get("/balance") - initial_balance = response.json()["balance"] - response = client.post("/invoice?amount=100") - response = client.get("/balance") - assert response.json()["balance"] == initial_balance + 100 - response = client.post("/send?amount=50") - response = client.get("/balance") - assert response.json()["balance"] == initial_balance + 50 - response = client.post("/send?amount=50") - response = client.get("/balance") - assert response.json()["balance"] == initial_balance - response = client.get("/pending") - token = response.json()["pending_token"]["0"]["token"] - amount = response.json()["pending_token"]["0"]["amount"] - response = client.post(f"/receive?token={token}") - response = client.get("/balance") - assert response.json()["balance"] == initial_balance + amount + response = client.get("/balance") + initial_balance = response.json()["balance"] + response = client.post("/lightning/create_invoice?amount=100") + invoice_response = InvoiceResponse.parse_obj(response.json()) + state = PaymentStatus(paid=False) + while not state.paid: + print("checking invoice state") + response2 = client.get( + f"/lightning/invoice_state?payment_hash={invoice_response.checking_id}" + ) + state = PaymentStatus.parse_obj(response2.json()) + await asyncio.sleep(0.1) + print("state:", state) + + response = client.get("/balance") + assert response.json()["balance"] == initial_balance + 100 + response = client.post("/send?amount=50") + response = client.get("/balance") + assert response.json()["balance"] == initial_balance + 50 + response = client.post("/send?amount=50") + response = client.get("/balance") + assert response.json()["balance"] == initial_balance + response = client.get("/pending") + token = response.json()["pending_token"]["0"]["token"] + amount = response.json()["pending_token"]["0"]["amount"] + response = client.post(f"/receive?token={token}") + response = client.get("/balance") + assert response.json()["balance"] == initial_balance + amount diff --git a/tests/test_wallet_htlc.py b/tests/test_wallet_htlc.py index f518b235..f6036d25 100644 --- a/tests/test_wallet_htlc.py +++ b/tests/test_wallet_htlc.py @@ -59,7 +59,7 @@ async def wallet2(mint): @pytest.mark.asyncio async def test_create_htlc_secret(wallet1: Wallet): invoice = await wallet1.request_mint(64) - await wallet1.mint(64, hash=invoice.hash) + await wallet1.mint(64, id=invoice.id) preimage = "00000000000000000000000000000000" preimage_hash = hashlib.sha256(bytes.fromhex(preimage)).hexdigest() secret = await wallet1.create_htlc_lock(preimage=preimage) @@ -69,7 +69,7 @@ async def test_create_htlc_secret(wallet1: Wallet): @pytest.mark.asyncio async def test_htlc_split(wallet1: Wallet, wallet2: Wallet): invoice = await wallet1.request_mint(64) - await wallet1.mint(64, hash=invoice.hash) + await wallet1.mint(64, id=invoice.id) preimage = "00000000000000000000000000000000" preimage_hash = hashlib.sha256(bytes.fromhex(preimage)).hexdigest() secret = await wallet1.create_htlc_lock(preimage=preimage) @@ -82,7 +82,7 @@ async def test_htlc_split(wallet1: Wallet, wallet2: Wallet): @pytest.mark.asyncio async def test_htlc_redeem_with_preimage(wallet1: Wallet, wallet2: Wallet): invoice = await wallet1.request_mint(64) - await wallet1.mint(64, hash=invoice.hash) + await wallet1.mint(64, id=invoice.id) preimage = "00000000000000000000000000000000" # preimage_hash = hashlib.sha256(bytes.fromhex(preimage)).hexdigest() secret = await wallet1.create_htlc_lock(preimage=preimage) @@ -96,7 +96,7 @@ async def test_htlc_redeem_with_preimage(wallet1: Wallet, wallet2: Wallet): @pytest.mark.asyncio async def test_htlc_redeem_with_wrong_preimage(wallet1: Wallet, wallet2: Wallet): invoice = await wallet1.request_mint(64) - await wallet1.mint(64, hash=invoice.hash) + await wallet1.mint(64, id=invoice.id) preimage = "00000000000000000000000000000000" # preimage_hash = hashlib.sha256(bytes.fromhex(preimage)).hexdigest() secret = await wallet1.create_htlc_lock( @@ -114,7 +114,7 @@ async def test_htlc_redeem_with_wrong_preimage(wallet1: Wallet, wallet2: Wallet) @pytest.mark.asyncio async def test_htlc_redeem_with_no_signature(wallet1: Wallet, wallet2: Wallet): invoice = await wallet1.request_mint(64) - await wallet1.mint(64, hash=invoice.hash) + await wallet1.mint(64, id=invoice.id) preimage = "00000000000000000000000000000000" pubkey_wallet1 = await wallet1.create_p2pk_pubkey() # preimage_hash = hashlib.sha256(bytes.fromhex(preimage)).hexdigest() @@ -134,7 +134,7 @@ async def test_htlc_redeem_with_no_signature(wallet1: Wallet, wallet2: Wallet): @pytest.mark.asyncio async def test_htlc_redeem_with_wrong_signature(wallet1: Wallet, wallet2: Wallet): invoice = await wallet1.request_mint(64) - await wallet1.mint(64, hash=invoice.hash) + await wallet1.mint(64, id=invoice.id) preimage = "00000000000000000000000000000000" pubkey_wallet1 = await wallet1.create_p2pk_pubkey() # preimage_hash = hashlib.sha256(bytes.fromhex(preimage)).hexdigest() @@ -159,7 +159,7 @@ async def test_htlc_redeem_with_wrong_signature(wallet1: Wallet, wallet2: Wallet @pytest.mark.asyncio async def test_htlc_redeem_with_correct_signature(wallet1: Wallet, wallet2: Wallet): invoice = await wallet1.request_mint(64) - await wallet1.mint(64, hash=invoice.hash) + await wallet1.mint(64, id=invoice.id) preimage = "00000000000000000000000000000000" pubkey_wallet1 = await wallet1.create_p2pk_pubkey() # preimage_hash = hashlib.sha256(bytes.fromhex(preimage)).hexdigest() @@ -181,7 +181,7 @@ async def test_htlc_redeem_hashlock_wrong_signature_timelock_correct_signature( wallet1: Wallet, wallet2: Wallet ): invoice = await wallet1.request_mint(64) - await wallet1.mint(64, hash=invoice.hash) + await wallet1.mint(64, id=invoice.id) preimage = "00000000000000000000000000000000" pubkey_wallet1 = await wallet1.create_p2pk_pubkey() pubkey_wallet2 = await wallet2.create_p2pk_pubkey() @@ -215,7 +215,7 @@ async def test_htlc_redeem_hashlock_wrong_signature_timelock_wrong_signature( wallet1: Wallet, wallet2: Wallet ): invoice = await wallet1.request_mint(64) - await wallet1.mint(64, hash=invoice.hash) + await wallet1.mint(64, id=invoice.id) preimage = "00000000000000000000000000000000" pubkey_wallet1 = await wallet1.create_p2pk_pubkey() pubkey_wallet2 = await wallet2.create_p2pk_pubkey() diff --git a/tests/test_wallet_p2pk.py b/tests/test_wallet_p2pk.py index 10dd7750..2dbd5bfb 100644 --- a/tests/test_wallet_p2pk.py +++ b/tests/test_wallet_p2pk.py @@ -60,7 +60,7 @@ async def wallet2(mint): @pytest.mark.asyncio async def test_create_p2pk_pubkey(wallet1: Wallet): invoice = await wallet1.request_mint(64) - await wallet1.mint(64, hash=invoice.hash) + await wallet1.mint(64, id=invoice.id) pubkey = await wallet1.create_p2pk_pubkey() PublicKey(bytes.fromhex(pubkey), raw=True) @@ -68,7 +68,7 @@ async def test_create_p2pk_pubkey(wallet1: Wallet): @pytest.mark.asyncio async def test_p2pk(wallet1: Wallet, wallet2: Wallet): invoice = await wallet1.request_mint(64) - await wallet1.mint(64, hash=invoice.hash) + await wallet1.mint(64, id=invoice.id) pubkey_wallet2 = await wallet2.create_p2pk_pubkey() # p2pk test secret_lock = await wallet1.create_p2pk_lock(pubkey_wallet2) # sender side @@ -81,7 +81,7 @@ async def test_p2pk(wallet1: Wallet, wallet2: Wallet): @pytest.mark.asyncio async def test_p2pk_sig_all(wallet1: Wallet, wallet2: Wallet): invoice = await wallet1.request_mint(64) - await wallet1.mint(64, hash=invoice.hash) + await wallet1.mint(64, id=invoice.id) pubkey_wallet2 = await wallet2.create_p2pk_pubkey() # p2pk test secret_lock = await wallet1.create_p2pk_lock( @@ -96,7 +96,7 @@ async def test_p2pk_sig_all(wallet1: Wallet, wallet2: Wallet): @pytest.mark.asyncio async def test_p2pk_receive_with_wrong_private_key(wallet1: Wallet, wallet2: Wallet): invoice = await wallet1.request_mint(64) - await wallet1.mint(64, hash=invoice.hash) + await wallet1.mint(64, id=invoice.id) pubkey_wallet2 = await wallet2.create_p2pk_pubkey() # receiver side # sender side secret_lock = await wallet1.create_p2pk_lock(pubkey_wallet2) # sender side @@ -116,7 +116,7 @@ async def test_p2pk_short_locktime_receive_with_wrong_private_key( wallet1: Wallet, wallet2: Wallet ): invoice = await wallet1.request_mint(64) - await wallet1.mint(64, hash=invoice.hash) + await wallet1.mint(64, id=invoice.id) pubkey_wallet2 = await wallet2.create_p2pk_pubkey() # receiver side # sender side secret_lock = await wallet1.create_p2pk_lock( @@ -141,7 +141,7 @@ async def test_p2pk_short_locktime_receive_with_wrong_private_key( @pytest.mark.asyncio async def test_p2pk_locktime_with_refund_pubkey(wallet1: Wallet, wallet2: Wallet): invoice = await wallet1.request_mint(64) - await wallet1.mint(64, hash=invoice.hash) + await wallet1.mint(64, id=invoice.id) pubkey_wallet2 = await wallet2.create_p2pk_pubkey() # receiver side # sender side garbage_pubkey = PrivateKey().pubkey @@ -169,7 +169,7 @@ async def test_p2pk_locktime_with_refund_pubkey(wallet1: Wallet, wallet2: Wallet @pytest.mark.asyncio async def test_p2pk_locktime_with_wrong_refund_pubkey(wallet1: Wallet, wallet2: Wallet): invoice = await wallet1.request_mint(64) - await wallet1.mint(64, hash=invoice.hash) + await wallet1.mint(64, id=invoice.id) await wallet2.create_p2pk_pubkey() # receiver side # sender side garbage_pubkey = PrivateKey().pubkey @@ -204,7 +204,7 @@ async def test_p2pk_locktime_with_second_refund_pubkey( wallet1: Wallet, wallet2: Wallet ): invoice = await wallet1.request_mint(64) - await wallet1.mint(64, hash=invoice.hash) + await wallet1.mint(64, id=invoice.id) pubkey_wallet1 = await wallet1.create_p2pk_pubkey() # receiver side pubkey_wallet2 = await wallet2.create_p2pk_pubkey() # receiver side # sender side @@ -235,7 +235,7 @@ async def test_p2pk_locktime_with_second_refund_pubkey( @pytest.mark.asyncio async def test_p2pk_multisig_2_of_2(wallet1: Wallet, wallet2: Wallet): invoice = await wallet1.request_mint(64) - await wallet1.mint(64, hash=invoice.hash) + await wallet1.mint(64, id=invoice.id) pubkey_wallet1 = await wallet1.create_p2pk_pubkey() pubkey_wallet2 = await wallet2.create_p2pk_pubkey() assert pubkey_wallet1 != pubkey_wallet2 @@ -256,7 +256,7 @@ async def test_p2pk_multisig_2_of_2(wallet1: Wallet, wallet2: Wallet): @pytest.mark.asyncio async def test_p2pk_multisig_duplicate_signature(wallet1: Wallet, wallet2: Wallet): invoice = await wallet1.request_mint(64) - await wallet1.mint(64, hash=invoice.hash) + await wallet1.mint(64, id=invoice.id) pubkey_wallet1 = await wallet1.create_p2pk_pubkey() pubkey_wallet2 = await wallet2.create_p2pk_pubkey() assert pubkey_wallet1 != pubkey_wallet2 @@ -279,7 +279,7 @@ async def test_p2pk_multisig_duplicate_signature(wallet1: Wallet, wallet2: Walle @pytest.mark.asyncio async def test_p2pk_multisig_quorum_not_met_1_of_2(wallet1: Wallet, wallet2: Wallet): invoice = await wallet1.request_mint(64) - await wallet1.mint(64, hash=invoice.hash) + await wallet1.mint(64, id=invoice.id) pubkey_wallet1 = await wallet1.create_p2pk_pubkey() pubkey_wallet2 = await wallet2.create_p2pk_pubkey() assert pubkey_wallet1 != pubkey_wallet2 @@ -299,7 +299,7 @@ async def test_p2pk_multisig_quorum_not_met_1_of_2(wallet1: Wallet, wallet2: Wal @pytest.mark.asyncio async def test_p2pk_multisig_quorum_not_met_2_of_3(wallet1: Wallet, wallet2: Wallet): invoice = await wallet1.request_mint(64) - await wallet1.mint(64, hash=invoice.hash) + await wallet1.mint(64, id=invoice.id) pubkey_wallet1 = await wallet1.create_p2pk_pubkey() pubkey_wallet2 = await wallet2.create_p2pk_pubkey() assert pubkey_wallet1 != pubkey_wallet2 @@ -323,7 +323,7 @@ async def test_p2pk_multisig_quorum_not_met_2_of_3(wallet1: Wallet, wallet2: Wal @pytest.mark.asyncio async def test_p2pk_multisig_with_duplicate_publickey(wallet1: Wallet, wallet2: Wallet): invoice = await wallet1.request_mint(64) - await wallet1.mint(64, hash=invoice.hash) + await wallet1.mint(64, id=invoice.id) pubkey_wallet2 = await wallet2.create_p2pk_pubkey() # p2pk test secret_lock = await wallet1.create_p2pk_lock( @@ -340,7 +340,7 @@ async def test_p2pk_multisig_with_wrong_first_private_key( wallet1: Wallet, wallet2: Wallet ): invoice = await wallet1.request_mint(64) - await wallet1.mint(64, hash=invoice.hash) + await wallet1.mint(64, id=invoice.id) await wallet1.create_p2pk_pubkey() pubkey_wallet2 = await wallet2.create_p2pk_pubkey() wrong_pubklic_key = PrivateKey().pubkey diff --git a/tests/test_wallet_restore.py b/tests/test_wallet_restore.py index 7a52d84a..ebb439ba 100644 --- a/tests/test_wallet_restore.py +++ b/tests/test_wallet_restore.py @@ -147,7 +147,7 @@ async def test_generate_secrets_from_to(wallet3: Wallet): async def test_restore_wallet_after_mint(wallet3: Wallet): await reset_wallet_db(wallet3) invoice = await wallet3.request_mint(64) - await wallet3.mint(64, hash=invoice.hash) + await wallet3.mint(64, id=invoice.id) assert wallet3.balance == 64 await reset_wallet_db(wallet3) await wallet3.load_proofs() @@ -177,7 +177,7 @@ async def test_restore_wallet_after_split_to_send(wallet3: Wallet): await reset_wallet_db(wallet3) invoice = await wallet3.request_mint(64) - await wallet3.mint(64, hash=invoice.hash) + await wallet3.mint(64, id=invoice.id) assert wallet3.balance == 64 _, spendable_proofs = await wallet3.split_to_send(wallet3.proofs, 32, set_reserved=True) # type: ignore @@ -199,7 +199,7 @@ async def test_restore_wallet_after_send_and_receive(wallet3: Wallet, wallet2: W ) await reset_wallet_db(wallet3) invoice = await wallet3.request_mint(64) - await wallet3.mint(64, hash=invoice.hash) + await wallet3.mint(64, id=invoice.id) assert wallet3.balance == 64 _, spendable_proofs = await wallet3.split_to_send(wallet3.proofs, 32, set_reserved=True) # type: ignore @@ -239,7 +239,7 @@ async def test_restore_wallet_after_send_and_self_receive(wallet3: Wallet): await reset_wallet_db(wallet3) invoice = await wallet3.request_mint(64) - await wallet3.mint(64, hash=invoice.hash) + await wallet3.mint(64, id=invoice.id) assert wallet3.balance == 64 _, spendable_proofs = await wallet3.split_to_send(wallet3.proofs, 32, set_reserved=True) # type: ignore @@ -265,7 +265,7 @@ async def test_restore_wallet_after_send_twice( await reset_wallet_db(wallet3) invoice = await wallet3.request_mint(2) - await wallet3.mint(2, hash=invoice.hash) + await wallet3.mint(2, id=invoice.id) box.add(wallet3.proofs) assert wallet3.balance == 2 @@ -319,7 +319,7 @@ async def test_restore_wallet_after_send_and_self_receive_nonquadratic_value( await reset_wallet_db(wallet3) invoice = await wallet3.request_mint(64) - await wallet3.mint(64, hash=invoice.hash) + await wallet3.mint(64, id=invoice.id) box.add(wallet3.proofs) assert wallet3.balance == 64