From 1b60b183fb1bbe193cc5f0d302074ccb2c2bd16a Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 10 Jul 2024 01:46:00 +0200 Subject: [PATCH 1/2] wip: pydantic 2.0 --- cashu/core/base.py | 32 +++--- cashu/core/htlc.py | 2 +- cashu/core/models.py | 56 +++++----- cashu/core/p2pk.py | 4 +- cashu/core/secret.py | 30 ++--- cashu/core/settings.py | 45 ++++---- cashu/lightning/base.py | 2 +- cashu/mint/crud.py | 2 +- cashu/mint/events/client.py | 28 +++-- cashu/mint/events/events.py | 4 +- cashu/mint/router_deprecated.py | 4 +- cashu/mint/startup.py | 2 +- cashu/wallet/api/responses.py | 4 +- cashu/wallet/api/router.py | 11 +- cashu/wallet/cli/cli.py | 4 +- cashu/wallet/crud.py | 2 +- cashu/wallet/mint_info.py | 18 +-- cashu/wallet/subscriptions.py | 6 +- cashu/wallet/v1_api.py | 36 +++--- cashu/wallet/wallet.py | 2 +- cashu/wallet/wallet_deprecated.py | 40 ++++--- poetry.lock | 178 +++++++++++++++++++++--------- pyproject.toml | 2 +- tests/test_core.py | 6 +- tests/test_crypto.py | 2 +- tests/test_mint_api.py | 18 +-- tests/test_mint_api_deprecated.py | 18 +-- tests/test_wallet_api.py | 8 +- tests/test_wallet_subscription.py | 6 +- 29 files changed, 335 insertions(+), 237 deletions(-) diff --git a/cashu/core/base.py b/cashu/core/base.py index 96c7313d..ba849f93 100644 --- a/cashu/core/base.py +++ b/cashu/core/base.py @@ -8,7 +8,7 @@ import cbor2 from loguru import logger -from pydantic import BaseModel, root_validator +from pydantic import BaseModel from cashu.core.json_rpc.base import JSONRPCSubscriptionKinds @@ -63,12 +63,13 @@ class ProofState(LedgerEvent): state: ProofSpentState witness: Optional[str] = None - @root_validator() - def check_witness(cls, values): - state, witness = values.get("state"), values.get("witness") - if witness is not None and state != ProofSpentState.spent: - raise ValueError('Witness can only be set if the spent state is "SPENT"') - return values + # @model_validator(mode="wrap") + # @classmethod + # def check_witness(cls, values): + # state, witness = values.get("state"), values.get("witness") + # if witness is not None and state != ProofSpentState.spent: + # raise ValueError('Witness can only be set if the spent state is "SPENT"') + # return values @property def identifier(self) -> str: @@ -132,8 +133,8 @@ class Proof(BaseModel): reserved: Union[None, bool] = False # unique ID of send attempt, used for grouping pending tokens in the wallet send_id: Union[None, str] = "" - time_created: Union[None, str] = "" - time_reserved: Union[None, str] = "" + time_created: Optional[int] = None + time_reserved: Optional[int] = None derivation_path: Union[None, str] = "" # derivation path of the proof mint_id: Union[ None, str @@ -163,7 +164,7 @@ def to_dict(self, include_dleq=False): # optional fields if include_dleq: assert self.dleq, "DLEQ proof is missing" - return_dict["dleq"] = self.dleq.dict() # type: ignore + return_dict["dleq"] = self.dleq.model_dump() # type: ignore if self.witness: return_dict["witness"] = self.witness @@ -195,11 +196,6 @@ def htlcpreimage(self) -> Union[str, None]: return HTLCWitness.from_witness(self.witness).preimage -class Proofs(BaseModel): - # NOTE: not used in Pydantic validation - __root__: List[Proof] - - class BlindedMessage(BaseModel): """ Blinded message or blinded secret or "output" which is to be signed by the mint @@ -806,7 +802,7 @@ def deserialize(cls, tokenv3_serialized: str) -> "TokenV3": token_base64 += "=" * (4 - len(token_base64) % 4) token = json.loads(base64.urlsafe_b64decode(token_base64)) - return cls.parse_obj(token) + return cls.model_validate(token) def serialize(self, include_dleq=False) -> str: """ @@ -966,7 +962,7 @@ def from_tokenv3(cls, tokenv3: TokenV3): return cls(t=cls.t, d=cls.d, m=cls.m, u=cls.u) def serialize_to_dict(self, include_dleq=False): - return_dict: Dict[str, Any] = dict(t=[t.dict() for t in self.t]) + return_dict: Dict[str, Any] = dict(t=[t.model_dump() for t in self.t]) # strip dleq if needed if not include_dleq: for token in return_dict["t"]: @@ -1013,7 +1009,7 @@ def deserialize(cls, tokenv4_serialized: str) -> "TokenV4": token_base64 += "=" * (4 - len(token_base64) % 4) token = cbor2.loads(base64.urlsafe_b64decode(token_base64)) - return cls.parse_obj(token) + return cls.model_validate(token) def to_tokenv3(self) -> TokenV3: tokenv3 = TokenV3() diff --git a/cashu/core/htlc.py b/cashu/core/htlc.py index d75f7c2e..8e25fc88 100644 --- a/cashu/core/htlc.py +++ b/cashu/core/htlc.py @@ -7,7 +7,7 @@ class HTLCSecret(Secret): @classmethod def from_secret(cls, secret: Secret): assert SecretKind(secret.kind) == SecretKind.HTLC, "Secret is not a HTLC secret" - # NOTE: exclude tags in .dict() because it doesn't deserialize it properly + # NOTE: exclude tags in .model_dump() because it doesn't deserialize it properly # need to add it back in manually with tags=secret.tags return cls(**secret.dict(exclude={"tags"}), tags=secret.tags) diff --git a/cashu/core/models.py b/cashu/core/models.py index f4cea2b1..6feda5d5 100644 --- a/cashu/core/models.py +++ b/cashu/core/models.py @@ -1,6 +1,6 @@ from typing import Any, Dict, List, Optional, Union -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, RootModel from .base import ( BlindedMessage, @@ -81,8 +81,8 @@ class KeysetsResponse(BaseModel): keysets: list[KeysetsResponseKeyset] -class KeysResponse_deprecated(BaseModel): - __root__: Dict[str, str] +class KeysResponse_deprecated(RootModel[Dict[str, str]]): + pass class KeysetsResponse_deprecated(BaseModel): @@ -102,16 +102,16 @@ class PostMintQuoteResponse(BaseModel): request: str # input payment request paid: Optional[ bool - ] # whether the request has been paid # DEPRECATED as per NUT PR #141 + ] = None # whether the request has been paid # DEPRECATED as per NUT PR #141 state: str # state of the quote - expiry: Optional[int] # expiry of the quote + expiry: Optional[int] = None # expiry of the quote @classmethod def from_mint_quote(self, mint_quote: MintQuote) -> "PostMintQuoteResponse": - to_dict = mint_quote.dict() + to_dict = mint_quote.model_dump() # turn state into string to_dict["state"] = mint_quote.state.value - return PostMintQuoteResponse.parse_obj(to_dict) + return PostMintQuoteResponse.model_validate(to_dict) # ------- API: MINT ------- @@ -120,7 +120,7 @@ def from_mint_quote(self, mint_quote: MintQuote) -> "PostMintQuoteResponse": class PostMintRequest(BaseModel): quote: str = Field(..., max_length=settings.mint_max_request_length) # quote id outputs: List[BlindedMessage] = Field( - ..., max_items=settings.mint_max_request_length + ..., max_length=settings.mint_max_request_length ) @@ -135,7 +135,7 @@ class GetMintResponse_deprecated(BaseModel): class PostMintRequest_deprecated(BaseModel): outputs: List[BlindedMessage_Deprecated] = Field( - ..., max_items=settings.mint_max_request_length + ..., max_length=settings.mint_max_request_length ) @@ -151,7 +151,7 @@ class PostMeltRequestOptionMpp(BaseModel): class PostMeltRequestOptions(BaseModel): - mpp: Optional[PostMeltRequestOptionMpp] + mpp: Optional[PostMeltRequestOptionMpp] = None class PostMeltQuoteRequest(BaseModel): @@ -182,16 +182,16 @@ class PostMeltQuoteResponse(BaseModel): fee_reserve: int # input fee reserve paid: bool # whether the request has been paid # DEPRECATED as per NUT PR #136 state: str # state of the quote - expiry: Optional[int] # expiry of the quote + expiry: Optional[int] = None # expiry of the quote payment_preimage: Optional[str] = None # payment preimage change: Union[List[BlindedSignature], None] = None @classmethod def from_melt_quote(self, melt_quote: MeltQuote) -> "PostMeltQuoteResponse": - to_dict = melt_quote.dict() + to_dict = melt_quote.model_dump() # turn state into string to_dict["state"] = melt_quote.state.value - return PostMeltQuoteResponse.parse_obj(to_dict) + return PostMeltQuoteResponse.model_validate(to_dict) # ------- API: MELT ------- @@ -199,23 +199,23 @@ def from_melt_quote(self, melt_quote: MeltQuote) -> "PostMeltQuoteResponse": class PostMeltRequest(BaseModel): quote: str = Field(..., max_length=settings.mint_max_request_length) # quote id - inputs: List[Proof] = Field(..., max_items=settings.mint_max_request_length) + inputs: List[Proof] = Field(..., max_length=settings.mint_max_request_length) outputs: Union[List[BlindedMessage], None] = Field( - None, max_items=settings.mint_max_request_length + None, max_length=settings.mint_max_request_length ) class PostMeltResponse_deprecated(BaseModel): - paid: Union[bool, None] - preimage: Union[str, None] + paid: Union[bool, None] = None + preimage: Union[str, None] = None change: Union[List[BlindedSignature], None] = None class PostMeltRequest_deprecated(BaseModel): - proofs: List[Proof] = Field(..., max_items=settings.mint_max_request_length) + proofs: List[Proof] = Field(..., max_length=settings.mint_max_request_length) pr: str = Field(..., max_length=settings.mint_max_request_length) outputs: Union[List[BlindedMessage_Deprecated], None] = Field( - None, max_items=settings.mint_max_request_length + None, max_length=settings.mint_max_request_length ) @@ -223,9 +223,9 @@ class PostMeltRequest_deprecated(BaseModel): class PostSplitRequest(BaseModel): - inputs: List[Proof] = Field(..., max_items=settings.mint_max_request_length) + inputs: List[Proof] = Field(..., max_length=settings.mint_max_request_length) outputs: List[BlindedMessage] = Field( - ..., max_items=settings.mint_max_request_length + ..., max_length=settings.mint_max_request_length ) @@ -235,10 +235,10 @@ class PostSplitResponse(BaseModel): # deprecated since 0.13.0 class PostSplitRequest_Deprecated(BaseModel): - proofs: List[Proof] = Field(..., max_items=settings.mint_max_request_length) + proofs: List[Proof] = Field(..., max_length=settings.mint_max_request_length) amount: Optional[int] = None outputs: List[BlindedMessage_Deprecated] = Field( - ..., max_items=settings.mint_max_request_length + ..., max_length=settings.mint_max_request_length ) @@ -256,7 +256,7 @@ class PostSplitResponse_Very_Deprecated(BaseModel): class PostCheckStateRequest(BaseModel): - Ys: List[str] = Field(..., max_items=settings.mint_max_request_length) + Ys: List[str] = Field(..., max_length=settings.mint_max_request_length) class PostCheckStateResponse(BaseModel): @@ -264,7 +264,7 @@ class PostCheckStateResponse(BaseModel): class CheckSpendableRequest_deprecated(BaseModel): - proofs: List[Proof] = Field(..., max_items=settings.mint_max_request_length) + proofs: List[Proof] = Field(..., max_length=settings.mint_max_request_length) class CheckSpendableResponse_deprecated(BaseModel): @@ -277,7 +277,7 @@ class CheckFeesRequest_deprecated(BaseModel): class CheckFeesResponse_deprecated(BaseModel): - fee: Union[int, None] + fee: Union[int, None] = None # ------- API: RESTORE ------- @@ -285,13 +285,13 @@ class CheckFeesResponse_deprecated(BaseModel): class PostRestoreRequest(BaseModel): outputs: List[BlindedMessage] = Field( - ..., max_items=settings.mint_max_request_length + ..., max_length=settings.mint_max_request_length ) class PostRestoreRequest_Deprecated(BaseModel): outputs: List[BlindedMessage_Deprecated] = Field( - ..., max_items=settings.mint_max_request_length + ..., max_length=settings.mint_max_request_length ) diff --git a/cashu/core/p2pk.py b/cashu/core/p2pk.py index f42a3a95..fa5796d0 100644 --- a/cashu/core/p2pk.py +++ b/cashu/core/p2pk.py @@ -20,9 +20,9 @@ class P2PKSecret(Secret): @classmethod def from_secret(cls, secret: Secret): assert SecretKind(secret.kind) == SecretKind.P2PK, "Secret is not a P2PK secret" - # NOTE: exclude tags in .dict() because it doesn't deserialize it properly + # NOTE: exclude tags in .model_dump() because it doesn't deserialize it properly # need to add it back in manually with tags=secret.tags - return cls(**secret.dict(exclude={"tags"}), tags=secret.tags) + return cls(**secret.model_dump(exclude={"tags"}), tags=secret.tags) def get_p2pk_pubkey_from_secret(self) -> List[str]: """Gets the P2PK pubkey from a Secret depending on the locktime. diff --git a/cashu/core/secret.py b/cashu/core/secret.py index 663f2be5..20a121a0 100644 --- a/cashu/core/secret.py +++ b/cashu/core/secret.py @@ -3,7 +3,7 @@ from typing import Any, Dict, List, Optional, Union from loguru import logger -from pydantic import BaseModel +from pydantic import BaseModel, RootModel from .crypto.secp import PrivateKey @@ -13,39 +13,39 @@ class SecretKind(Enum): HTLC = "HTLC" -class Tags(BaseModel): +class Tags(RootModel[List[List[str]]]): """ Tags are used to encode additional information in the Secret of a Proof. """ - __root__: List[List[str]] = [] + root: List[List[str]] = [] def __init__(self, tags: Optional[List[List[str]]] = None, **kwargs): super().__init__(**kwargs) - self.__root__ = tags or [] + self.root = tags or [] def __setitem__(self, key: str, value: Union[str, List[str]]) -> None: if isinstance(value, str): - self.__root__.append([key, value]) + self.root.append([key, value]) elif isinstance(value, list): - self.__root__.append([key, *value]) + self.root.append([key, *value]) def __getitem__(self, key: str) -> Union[str, None]: return self.get_tag(key) def get_tag(self, tag_name: str) -> Union[str, None]: - for tag in self.__root__: + for tag in self.root: if tag[0] == tag_name: return tag[1] return None def get_tag_all(self, tag_name: str) -> List[str]: - all_tags = [] - for tag in self.__root__: + allroot = [] + for tag in self.root: if tag[0] == tag_name: for t in tag[1:]: - all_tags.append(t) - return all_tags + allroot.append(t) + return allroot class Secret(BaseModel): @@ -61,9 +61,9 @@ def serialize(self) -> str: "data": self.data, "nonce": self.nonce or PrivateKey().serialize()[:32], } - if self.tags.__root__: - logger.debug(f"Serializing tags: {self.tags.__root__}") - data_dict["tags"] = self.tags.__root__ + if self.tags.root: + logger.debug(f"Serializing tags: {self.tags.root}") + data_dict["tags"] = self.tags.root return json.dumps( [self.kind, data_dict], ) @@ -73,7 +73,7 @@ def deserialize(cls, from_proof: str): kind, kwargs = json.loads(from_proof) data = kwargs.pop("data") nonce = kwargs.pop("nonce") - tags_list: List = kwargs.pop("tags", None) + tags_list: List[List[str]] = kwargs.pop("tags", None) or [] tags = Tags(tags=tags_list) logger.debug(f"Deserialized Secret: {kind}, {data}, {nonce}, {tags}") return cls(kind=kind, data=data, nonce=nonce, tags=tags) diff --git a/cashu/core/settings.py b/cashu/core/settings.py index 377ecf5b..fc634c68 100644 --- a/cashu/core/settings.py +++ b/cashu/core/settings.py @@ -4,7 +4,8 @@ from typing import List, Optional from environs import Env # type: ignore -from pydantic import BaseSettings, Extra, Field +from pydantic import Field +from pydantic_settings import BaseSettings env = Env() @@ -24,16 +25,18 @@ def find_env_file(): class CashuSettings(BaseSettings): - env_file: str = Field(default=None) + env_file: Optional[str] = Field(default=None) lightning_fee_percent: float = Field(default=1.0) lightning_reserve_fee_min: int = Field(default=2000) max_order: int = Field(default=64) - class Config(BaseSettings.Config): + # TODO[pydantic]: The `Config` class inherits from another class, please create the `model_config` manually. + # Check https://docs.pydantic.dev/dev-v2/migration/#changes-to-config for more information. + class Config: env_file = find_env_file() env_file_encoding = "utf-8" case_sensitive = False - extra = Extra.ignore + extra = "ignore" # def __init__(self, env_file=None): # self.env_file = env_file or self.env_file @@ -50,7 +53,7 @@ class EnvSettings(CashuSettings): class MintSettings(CashuSettings): - mint_private_key: str = Field(default=None) + mint_private_key: str = Field(default="") mint_seed_decryption_key: Optional[str] = Field(default=None) mint_derivation_path: str = Field(default="m/0'/0'/0'") mint_derivation_path_list: List[str] = Field(default=[]) @@ -70,10 +73,10 @@ class MintBackends(MintSettings): mint_backend_bolt11_usd: str = Field(default="") mint_backend_bolt11_eur: str = Field(default="") - mint_lnbits_endpoint: str = Field(default=None) - mint_lnbits_key: str = Field(default=None) - mint_strike_key: str = Field(default=None) - mint_blink_key: str = Field(default=None) + mint_lnbits_endpoint: Optional[str] = Field(default=None) + mint_lnbits_key: Optional[str] = Field(default=None) + mint_strike_key: Optional[str] = Field(default=None) + mint_blink_key: Optional[str] = Field(default=None) class MintLimits(MintSettings): @@ -104,19 +107,19 @@ class MintLimits(MintSettings): title="Peg-out only", description="Mint allows no mint operations.", ) - mint_max_peg_in: int = Field( + mint_max_peg_in: Optional[int] = Field( default=None, gt=0, title="Maximum peg-in", description="Maximum amount for a mint operation.", ) - mint_max_peg_out: int = Field( + mint_max_peg_out: Optional[int] = Field( default=None, gt=0, title="Maximum peg-out", description="Maximum amount for a melt operation.", ) - mint_max_balance: int = Field( + mint_max_balance: Optional[int] = Field( default=None, gt=0, title="Maximum mint balance", @@ -140,20 +143,20 @@ class FakeWalletSettings(MintSettings): class MintInformation(CashuSettings): mint_info_name: str = Field(default="Cashu mint") - mint_info_description: str = Field(default=None) - mint_info_description_long: str = Field(default=None) + mint_info_description: Optional[str] = Field(default=None) + mint_info_description_long: Optional[str] = Field(default=None) mint_info_contact: List[List[str]] = Field(default=[["", ""]]) - mint_info_motd: str = Field(default=None) + mint_info_motd: Optional[str] = Field(default=None) class WalletSettings(CashuSettings): tor: bool = Field(default=False) - socks_host: str = Field(default=None) # deprecated - socks_port: int = Field(default=9050) # deprecated - socks_proxy: str = Field(default=None) - http_proxy: str = Field(default=None) - mint_url: str = Field(default=None) - mint_host: str = Field(default="8333.space") + socks_host: Optional[str] = Field(default=None) # deprecated + socks_port: Optional[int] = Field(default=9050) # deprecated + socks_proxy: Optional[str] = Field(default=None) + http_proxy: Optional[str] = Field(default=None) + mint_url: Optional[str] = Field(default=None) + mint_host: str = Field(default="localhost") mint_port: int = Field(default=3338) wallet_name: str = Field(default="wallet") wallet_unit: str = Field(default="sat") diff --git a/cashu/lightning/base.py b/cashu/lightning/base.py index 06fbe975..c6e1c9ec 100644 --- a/cashu/lightning/base.py +++ b/cashu/lightning/base.py @@ -12,7 +12,7 @@ class StatusResponse(BaseModel): - error_message: Optional[str] + error_message: Optional[str] = None balance: Union[int, float] diff --git a/cashu/mint/crud.py b/cashu/mint/crud.py index 0ef4af9d..054b9b53 100644 --- a/cashu/mint/crud.py +++ b/cashu/mint/crud.py @@ -633,7 +633,7 @@ async def update_melt_quote( db.timestamp_from_seconds(quote.paid_time) or "" ), "proof": quote.payment_preimage, - "change": json.dumps([s.dict() for s in quote.change]) + "change": json.dumps([s.model_dump() for s in quote.change]) if quote.change else None, "quote": quote.quote, diff --git a/cashu/mint/events/client.py b/cashu/mint/events/client.py index e54c0736..1a59779a 100644 --- a/cashu/mint/events/client.py +++ b/cashu/mint/events/client.py @@ -89,7 +89,7 @@ async def start(self): # Parse the JSONRPCRequest try: - req = JSONRPCRequest.parse_obj(data) + req = JSONRPCRequest.model_validate(data) except Exception as e: err = JSONRPCErrorResponse( error=JSONRPCError( @@ -136,7 +136,7 @@ async def start(self): async def _handle_request(self, data: JSONRPCRequest) -> JSONRPCResponse: logger.debug(f"Received websocket message: {data}") if data.method == JSONRPCMethods.SUBSCRIBE.value: - subscribe_params = JSONRPCSubscribeParams.parse_obj(data.params) + subscribe_params = JSONRPCSubscribeParams.model_validate(data.params) self.add_subscription( subscribe_params.kind, subscribe_params.filters, subscribe_params.subId ) @@ -144,22 +144,22 @@ async def _handle_request(self, data: JSONRPCRequest) -> JSONRPCResponse: status=JSONRPCStatus.OK, subId=subscribe_params.subId, ) - return JSONRPCResponse(result=result.dict(), id=data.id) + return JSONRPCResponse(result=result.model_dump(), id=data.id) elif data.method == JSONRPCMethods.UNSUBSCRIBE.value: - unsubscribe_params = JSONRPCUnsubscribeParams.parse_obj(data.params) + unsubscribe_params = JSONRPCUnsubscribeParams.model_validate(data.params) self.remove_subscription(unsubscribe_params.subId) result = JSONRRPCSubscribeResponse( status=JSONRPCStatus.OK, subId=unsubscribe_params.subId, ) - return JSONRPCResponse(result=result.dict(), id=data.id) + return JSONRPCResponse(result=result.model_dump(), id=data.id) else: raise ValueError(f"Invalid method: {data.method}") async def _send_obj(self, data: dict, subId: str): resp = JSONRPCNotification( method=JSONRPCMethods.SUBSCRIBE.value, - params=JSONRPCNotficationParams(subId=subId, payload=data).dict(), + params=JSONRPCNotficationParams(subId=subId, payload=data).model_dump(), ) await self._send_msg(resp) @@ -203,11 +203,15 @@ def remove_subscription(self, subId: str) -> None: def serialize_event(self, event: LedgerEvent) -> dict: if isinstance(event, MintQuote): - return_dict = PostMintQuoteResponse.parse_obj(event.dict()).dict() + return_dict = PostMintQuoteResponse.model_validate( + event.model_dump() + ).model_dump() elif isinstance(event, MeltQuote): - return_dict = PostMeltQuoteResponse.parse_obj(event.dict()).dict() + return_dict = PostMeltQuoteResponse.model_validate( + event.model_dump() + ).model_dump() elif isinstance(event, ProofState): - return_dict = event.dict(exclude_unset=True, exclude_none=True) + return_dict = event.model_dump(exclude_unset=True, exclude_none=True) return return_dict async def _init_subscription( @@ -218,14 +222,14 @@ async def _init_subscription( quote_id=filter, db=self.db_read.db ) if mint_quote: - await self._send_obj(mint_quote.dict(), subId) + await self._send_obj(mint_quote.model_dump(), subId) elif kind == JSONRPCSubscriptionKinds.BOLT11_MELT_QUOTE: melt_quote = await self.db_read.crud.get_melt_quote( quote_id=filter, db=self.db_read.db ) if melt_quote: - await self._send_obj(melt_quote.dict(), subId) + await self._send_obj(melt_quote.model_dump(), subId) elif kind == JSONRPCSubscriptionKinds.PROOF_STATE: proofs = await self.db_read.get_proofs_states(Ys=[filter]) if len(proofs): - await self._send_obj(proofs[0].dict(), subId) + await self._send_obj(proofs[0].model_dump(), subId) diff --git a/cashu/mint/events/events.py b/cashu/mint/events/events.py index f62c2875..9f7bd85a 100644 --- a/cashu/mint/events/events.py +++ b/cashu/mint/events/events.py @@ -38,9 +38,9 @@ def remove_client(self, client: LedgerEventClientManager) -> None: def serialize_event(self, event: LedgerEvent) -> dict: if isinstance(event, MintQuote): - return_dict = PostMintQuoteResponse.from_mint_quote(event).dict() + return_dict = PostMintQuoteResponse.from_mint_quote(event).model_dump() elif isinstance(event, MeltQuote): - return_dict = PostMeltQuoteResponse.from_melt_quote(event).dict() + return_dict = PostMeltQuoteResponse.from_melt_quote(event).model_dump() elif isinstance(event, ProofState): return_dict = event.dict(exclude_unset=True, exclude_none=True) return return_dict diff --git a/cashu/mint/router_deprecated.py b/cashu/mint/router_deprecated.py index 74d87c7e..63813a8c 100644 --- a/cashu/mint/router_deprecated.py +++ b/cashu/mint/router_deprecated.py @@ -75,7 +75,7 @@ async def keys_deprecated() -> Dict[str, str]: """This endpoint returns a dictionary of all supported token values of the mint and their associated public key.""" logger.trace("> GET /keys") keyset = ledger.get_keyset() - keys = KeysResponse_deprecated.parse_obj(keyset) + keys = KeysResponse_deprecated.model_validate(keyset) return keys.__root__ @@ -99,7 +99,7 @@ async def keyset_deprecated(idBase64Urlsafe: str) -> Dict[str, str]: logger.trace(f"> GET /keys/{idBase64Urlsafe}") id = idBase64Urlsafe.replace("-", "+").replace("_", "/") keyset = ledger.get_keyset(keyset_id=id) - keys = KeysResponse_deprecated.parse_obj(keyset) + keys = KeysResponse_deprecated.model_validate(keyset) return keys.__root__ diff --git a/cashu/mint/startup.py b/cashu/mint/startup.py index f846061f..e315a177 100644 --- a/cashu/mint/startup.py +++ b/cashu/mint/startup.py @@ -22,7 +22,7 @@ raise Exception("Nutshell cannot run in non-debug mode.") logger.debug("Enviroment Settings:") -for key, value in settings.dict().items(): +for key, value in settings.model_dump().items(): if key in [ "mint_private_key", "mint_seed_decryption_key", diff --git a/cashu/wallet/api/responses.py b/cashu/wallet/api/responses.py index db3532e0..b6dbdd5e 100644 --- a/cashu/wallet/api/responses.py +++ b/cashu/wallet/api/responses.py @@ -48,7 +48,7 @@ class PendingResponse(BaseModel): class LockResponse(BaseModel): - P2PK: Optional[str] + P2PK: Optional[str] = None class LocksResponse(BaseModel): @@ -73,7 +73,7 @@ class InfoResponse(BaseModel): debug: bool cashu_dir: str mint_urls: List[str] = [] - settings: Optional[str] + settings: Optional[str] = None tor: bool nostr_public_key: Optional[str] = None nostr_relays: List[str] = [] diff --git a/cashu/wallet/api/router.py b/cashu/wallet/api/router.py index 7ad15798..46b1330b 100644 --- a/cashu/wallet/api/router.py +++ b/cashu/wallet/api/router.py @@ -52,8 +52,11 @@ async def mint_wallet( mint_url: Optional[str] = None, raise_connection_error: bool = True ) -> LightningWallet: + url = mint_url or settings.mint_url + if not url: + raise Exception("No mint URL provided.") lightning_wallet = await LightningWallet.with_db( - mint_url or settings.mint_url, + url, db=os.path.join(settings.cashu_dir, settings.wallet_name), name=settings.wallet_name, ) @@ -92,7 +95,7 @@ async def pay( if mint: wallet = await mint_wallet(mint) payment_response = await wallet.pay_invoice(bolt11) - ret = PaymentResponse(**payment_response.dict()) + ret = PaymentResponse(**payment_response.model_dump()) ret.fee = None # TODO: we can't return an Amount object, overwriting return ret @@ -403,7 +406,9 @@ async def wallets(): pass result = {} for w in wallets: - wallet = Wallet(settings.mint_url, os.path.join(settings.cashu_dir, w), name=w) + url = settings.mint_url + assert url, "No mint URL provided." + wallet = Wallet(url, os.path.join(settings.cashu_dir, w), name=w) try: await init_wallet(wallet) if wallet.proofs and len(wallet.proofs): diff --git a/cashu/wallet/cli/cli.py b/cashu/wallet/cli/cli.py index 46dbbc79..132813bd 100644 --- a/cashu/wallet/cli/cli.py +++ b/cashu/wallet/cli/cli.py @@ -292,7 +292,7 @@ def mint_invoice_callback(msg: JSONRPCNotficationParams): if paid: return try: - quote = PostMintQuoteResponse.parse_obj(msg.payload) + quote = PostMintQuoteResponse.model_validate(msg.payload) except Exception: return logger.debug(f"Received callback for quote: {quote}") @@ -962,7 +962,7 @@ async def info(ctx: Context, mint: bool, mnemonic: bool): if mint: wallet.url = mint_url try: - mint_info: dict = (await wallet.load_mint_info()).dict() + mint_info: dict = (await wallet.load_mint_info()).model_dump() if mint_info: print(f" - Mint name: {mint_info['name']}") if mint_info.get("description"): diff --git a/cashu/wallet/crud.py b/cashu/wallet/crud.py index febd05d0..6e1460cb 100644 --- a/cashu/wallet/crud.py +++ b/cashu/wallet/crud.py @@ -24,7 +24,7 @@ async def store_proof( "secret": str(proof.secret), "time_created": int(time.time()), "derivation_path": proof.derivation_path, - "dleq": json.dumps(proof.dleq.dict()) if proof.dleq else "", + "dleq": json.dumps(proof.dleq.model_dump()) if proof.dleq else "", "mint_id": proof.mint_id, "melt_id": proof.melt_id, }, diff --git a/cashu/wallet/mint_info.py b/cashu/wallet/mint_info.py index 84035beb..60b81d8f 100644 --- a/cashu/wallet/mint_info.py +++ b/cashu/wallet/mint_info.py @@ -9,14 +9,14 @@ class MintInfo(BaseModel): - name: Optional[str] - pubkey: Optional[str] - version: Optional[str] - description: Optional[str] - description_long: Optional[str] - contact: Optional[List[List[str]]] - motd: Optional[str] - nuts: Optional[Dict[int, Any]] + name: Optional[str] = None + pubkey: Optional[str] = None + version: Optional[str] = None + description: Optional[str] = None + description_long: Optional[str] = None + contact: Optional[List[List[str]]] = None + motd: Optional[str] = None + nuts: Optional[Dict[int, Any]] = None def __str__(self): return f"{self.name} ({self.description})" @@ -34,7 +34,7 @@ def supports_mpp(self, method: str, unit: Unit) -> bool: return False for entry in nut_15: - entry_obj = Nut15MppSupport.parse_obj(entry) + entry_obj = Nut15MppSupport.model_validate(entry) if entry_obj.method == method and entry_obj.unit == unit.name: return True diff --git a/cashu/wallet/subscriptions.py b/cashu/wallet/subscriptions.py index a00aff18..887d34c1 100644 --- a/cashu/wallet/subscriptions.py +++ b/cashu/wallet/subscriptions.py @@ -52,7 +52,7 @@ def _on_message(self, ws, message): logger.error(f"Error parsing notification: {e}") return try: - params = JSONRPCNotficationParams.parse_obj(msg.params) + params = JSONRPCNotficationParams.model_validate(msg.params) logger.trace(f"Notification params: {params}") except Exception as e: logger.error(f"Error parsing notification params: {e}") @@ -69,7 +69,7 @@ def close(self): for subId in self.callback_map.keys(): req = JSONRPCRequest( method=JSONRPCMethods.UNSUBSCRIBE.value, - params=JSONRPCUnsubscribeParams(subId=subId).dict(), + params=JSONRPCUnsubscribeParams(subId=subId).model_dump(), id=self.id_counter, ) logger.trace(f"Unsubscribing: {req.json()}") @@ -92,7 +92,7 @@ def subscribe( method=JSONRPCMethods.SUBSCRIBE.value, params=JSONRPCSubscribeParams( kind=kind, filters=filters, subId=subId - ).dict(), + ).model_dump(), id=self.id_counter, ) logger.trace(f"Subscribing: {req.json()}") diff --git a/cashu/wallet/v1_api.py b/cashu/wallet/v1_api.py index 5acb7a90..364e5d3e 100644 --- a/cashu/wallet/v1_api.py +++ b/cashu/wallet/v1_api.py @@ -169,7 +169,7 @@ async def _get_keys(self) -> List[WalletKeyset]: self.raise_on_error_request(resp) keys_dict: dict = resp.json() assert len(keys_dict), Exception("did not receive any keys") - keys = KeysResponse.parse_obj(keys_dict) + keys = KeysResponse.model_validate(keys_dict) logger.debug( f"Received {len(keys.keysets)} keysets from mint:" f" {' '.join([k.id + f' ({k.unit})' for k in keys.keysets])}." @@ -216,7 +216,7 @@ async def _get_keyset(self, keyset_id: str) -> WalletKeyset: keys_dict = resp.json() assert len(keys_dict), Exception("did not receive any keys") - keys = KeysResponse.parse_obj(keys_dict) + keys = KeysResponse.model_validate(keys_dict) this_keyset = keys.keysets[0] keyset_keys = { int(amt): PublicKey(bytes.fromhex(val), raw=True) @@ -252,7 +252,7 @@ async def _get_keysets(self) -> List[KeysetsResponseKeyset]: self.raise_on_error_request(resp) keysets_dict = resp.json() - keysets = KeysetsResponse.parse_obj(keysets_dict).keysets + keysets = KeysetsResponse.model_validate(keysets_dict).keysets if not keysets: raise Exception("did not receive any keysets") return keysets @@ -278,7 +278,7 @@ async def _get_info(self) -> GetInfoResponse: # END backwards compatibility < 0.15.0 self.raise_on_error_request(resp) data: dict = resp.json() - mint_info: GetInfoResponse = GetInfoResponse.parse_obj(data) + mint_info: GetInfoResponse = GetInfoResponse.model_validate(data) return mint_info @async_set_httpx_client @@ -298,7 +298,7 @@ async def mint_quote(self, amount: int, unit: Unit) -> PostMintQuoteResponse: logger.trace("Requesting mint: GET /v1/mint/bolt11") payload = PostMintQuoteRequest(unit=unit.name, amount=amount) resp = await self.httpx.post( - join(self.url, "/v1/mint/quote/bolt11"), json=payload.dict() + join(self.url, "/v1/mint/quote/bolt11"), json=payload.model_dump() ) # BEGIN backwards compatibility < 0.15.0 # assume the mint has not upgraded yet if we get a 404 @@ -308,7 +308,7 @@ async def mint_quote(self, amount: int, unit: Unit) -> PostMintQuoteResponse: # END backwards compatibility < 0.15.0 self.raise_on_error_request(resp) return_dict = resp.json() - return PostMintQuoteResponse.parse_obj(return_dict) + return PostMintQuoteResponse.model_validate(return_dict) @async_set_httpx_client @async_ensure_mint_loaded @@ -352,7 +352,7 @@ def _mintrequest_include_fields(outputs: List[BlindedMessage]): self.raise_on_error_request(resp) response_dict = resp.json() logger.trace("Lightning invoice checked. POST /v1/mint/bolt11") - promises = PostMintResponse.parse_obj(response_dict).signatures + promises = PostMintResponse.model_validate(response_dict).signatures return promises @async_set_httpx_client @@ -376,7 +376,7 @@ async def melt_quote( resp = await self.httpx.post( join(self.url, "/v1/melt/quote/bolt11"), - json=payload.dict(), + json=payload.model_dump(), ) # BEGIN backwards compatibility < 0.15.0 # assume the mint has not upgraded yet if we get a 404 @@ -396,7 +396,7 @@ async def melt_quote( # END backwards compatibility < 0.15.0 self.raise_on_error_request(resp) return_dict = resp.json() - return PostMeltQuoteResponse.parse_obj(return_dict) + return PostMeltQuoteResponse.model_validate(return_dict) @async_set_httpx_client @async_ensure_mint_loaded @@ -454,7 +454,7 @@ def _meltrequest_include_fields( # END backwards compatibility < 0.15.0 self.raise_on_error_request(resp) return_dict = resp.json() - return PostMeltQuoteResponse.parse_obj(return_dict) + return PostMeltQuoteResponse.model_validate(return_dict) @async_set_httpx_client @async_ensure_mint_loaded @@ -494,8 +494,10 @@ def _splitrequest_include_fields(proofs: List[Proof]): # END backwards compatibility < 0.15.0 self.raise_on_error_request(resp) promises_dict = resp.json() - mint_response = PostSplitResponse.parse_obj(promises_dict) - promises = [BlindedSignature(**p.dict()) for p in mint_response.signatures] + mint_response = PostSplitResponse.model_validate(promises_dict) + promises = [ + BlindedSignature(**p.model_dump()) for p in mint_response.signatures + ] if len(promises) == 0: raise Exception("received no splits.") @@ -511,7 +513,7 @@ async def check_proof_state(self, proofs: List[Proof]) -> PostCheckStateResponse payload = PostCheckStateRequest(Ys=[p.Y for p in proofs]) resp = await self.httpx.post( join(self.url, "/v1/checkstate"), - json=payload.dict(), + json=payload.model_dump(), ) # BEGIN backwards compatibility < 0.15.0 # assume the mint has not upgraded yet if we get a 404 @@ -530,7 +532,7 @@ async def check_proof_state(self, proofs: List[Proof]) -> PostCheckStateResponse return ret # END backwards compatibility < 0.15.0 self.raise_on_error_request(resp) - return PostCheckStateResponse.parse_obj(resp.json()) + return PostCheckStateResponse.model_validate(resp.json()) @async_set_httpx_client @async_ensure_mint_loaded @@ -541,7 +543,9 @@ async def restore_promises( Asks the mint to restore promises corresponding to outputs. """ payload = PostMintRequest(quote="restore", outputs=outputs) - resp = await self.httpx.post(join(self.url, "/v1/restore"), json=payload.dict()) + resp = await self.httpx.post( + join(self.url, "/v1/restore"), json=payload.model_dump() + ) # BEGIN backwards compatibility < 0.15.0 # assume the mint has not upgraded yet if we get a 404 if resp.status_code == 404: @@ -550,7 +554,7 @@ async def restore_promises( # END backwards compatibility < 0.15.0 self.raise_on_error_request(resp) response_dict = resp.json() - returnObj = PostRestoreResponse.parse_obj(response_dict) + returnObj = PostRestoreResponse.model_validate(response_dict) # BEGIN backwards compatibility < 0.15.1 # if the mint returns promises, duplicate into signatures diff --git a/cashu/wallet/wallet.py b/cashu/wallet/wallet.py index 0c4bcd1d..1295837c 100644 --- a/cashu/wallet/wallet.py +++ b/cashu/wallet/wallet.py @@ -161,7 +161,7 @@ async def _migrate_database(self): async def load_mint_info(self) -> MintInfo: """Loads the mint info from the mint.""" mint_info_resp = await self._get_info() - self.mint_info = MintInfo(**mint_info_resp.dict()) + self.mint_info = MintInfo(**mint_info_resp.model_dump()) logger.debug(f"Mint info: {self.mint_info}") return self.mint_info diff --git a/cashu/wallet/wallet_deprecated.py b/cashu/wallet/wallet_deprecated.py index e5b953a4..c16e7f65 100644 --- a/cashu/wallet/wallet_deprecated.py +++ b/cashu/wallet/wallet_deprecated.py @@ -133,7 +133,7 @@ async def _get_info_deprecated(self) -> GetInfoResponse: self.raise_on_error(resp) data: dict = resp.json() mint_info_deprecated: GetInfoResponse_deprecated = ( - GetInfoResponse_deprecated.parse_obj(data) + GetInfoResponse_deprecated.model_validate(data) ) mint_info = GetInfoResponse( **mint_info_deprecated.dict(exclude={"parameter", "nuts"}) @@ -221,7 +221,7 @@ async def _get_keysets_deprecated(self, url: str) -> List[KeysetsResponseKeyset] ) self.raise_on_error(resp) keysets_dict = resp.json() - keysets = KeysetsResponse_deprecated.parse_obj(keysets_dict) + keysets = KeysetsResponse_deprecated.model_validate(keysets_dict) assert len(keysets.keysets), Exception("did not receive any keysets") keysets_new = [ KeysetsResponseKeyset(id=id, unit="sat", active=True) @@ -247,7 +247,7 @@ async def request_mint_deprecated(self, amount) -> PostMintQuoteResponse: resp = await self.httpx.get(self.url + "/mint", params={"amount": amount}) self.raise_on_error(resp) return_dict = resp.json() - mint_response = GetMintResponse_deprecated.parse_obj(return_dict) + mint_response = GetMintResponse_deprecated.model_validate(return_dict) decoded_invoice = bolt11.decode(mint_response.pr) return PostMintQuoteResponse( quote=mint_response.hash, @@ -274,7 +274,9 @@ async def mint_deprecated( Raises: Exception: If the minting fails """ - outputs_deprecated = [BlindedMessage_Deprecated(**o.dict()) for o in outputs] + outputs_deprecated = [ + BlindedMessage_Deprecated(**o.model_dump()) for o in outputs + ] outputs_payload = PostMintRequest_deprecated(outputs=outputs_deprecated) def _mintrequest_include_fields(outputs: List[BlindedMessage]): @@ -299,7 +301,7 @@ def _mintrequest_include_fields(outputs: List[BlindedMessage]): self.raise_on_error(resp) response_dict = resp.json() logger.trace("Lightning invoice checked. POST /mint") - promises = PostMintResponse_deprecated.parse_obj(response_dict).promises + promises = PostMintResponse_deprecated.model_validate(response_dict).promises return promises @async_set_httpx_client @@ -312,7 +314,7 @@ async def melt_deprecated( """ logger.warning("Using deprecated API call: POST /melt") outputs_deprecated = ( - [BlindedMessage_Deprecated(**o.dict()) for o in outputs] + [BlindedMessage_Deprecated(**o.model_dump()) for o in outputs] if outputs else None ) @@ -336,7 +338,7 @@ def _meltrequest_include_fields(proofs: List[Proof]): self.raise_on_error(resp) return_dict = resp.json() - return PostMeltResponse_deprecated.parse_obj(return_dict) + return PostMeltResponse_deprecated.model_validate(return_dict) @async_set_httpx_client @async_ensure_mint_loaded_deprecated @@ -347,7 +349,9 @@ async def split_deprecated( ) -> List[BlindedSignature]: """Consume proofs and create new promises based on amount split.""" logger.warning("Using deprecated API call: Calling split. POST /split") - outputs_deprecated = [BlindedMessage_Deprecated(**o.dict()) for o in outputs] + outputs_deprecated = [ + BlindedMessage_Deprecated(**o.model_dump()) for o in outputs + ] split_payload = PostSplitRequest_Deprecated( proofs=proofs, outputs=outputs_deprecated ) @@ -373,8 +377,8 @@ def _splitrequest_include_fields(proofs: List[Proof]): ) self.raise_on_error(resp) promises_dict = resp.json() - mint_response = PostSplitResponse_Deprecated.parse_obj(promises_dict) - promises = [BlindedSignature(**p.dict()) for p in mint_response.promises] + mint_response = PostSplitResponse_Deprecated.model_validate(promises_dict) + promises = [BlindedSignature(**p.model_dump()) for p in mint_response.promises] if len(promises) == 0: raise Exception("received no splits.") @@ -405,7 +409,7 @@ def _check_proof_state_include_fields(proofs): self.raise_on_error(resp) return_dict = resp.json() - states = CheckSpendableResponse_deprecated.parse_obj(return_dict) + states = CheckSpendableResponse_deprecated.model_validate(return_dict) return states @async_set_httpx_client @@ -417,12 +421,16 @@ async def restore_promises_deprecated( Asks the mint to restore promises corresponding to outputs. """ logger.warning("Using deprecated API call: POST /restore") - outputs_deprecated = [BlindedMessage_Deprecated(**o.dict()) for o in outputs] + outputs_deprecated = [ + BlindedMessage_Deprecated(**o.model_dump()) for o in outputs + ] payload = PostMintRequest_deprecated(outputs=outputs_deprecated) - resp = await self.httpx.post(join(self.url, "/restore"), json=payload.dict()) + resp = await self.httpx.post( + join(self.url, "/restore"), json=payload.model_dump() + ) self.raise_on_error(resp) response_dict = resp.json() - returnObj = PostRestoreResponse.parse_obj(response_dict) + returnObj = PostRestoreResponse.model_validate(response_dict) # BEGIN backwards compatibility < 0.15.1 # if the mint returns promises, duplicate into signatures @@ -439,9 +447,9 @@ async def check_fees_deprecated(self, payment_request: str): payload = CheckFeesRequest_deprecated(pr=payment_request) resp = await self.httpx.post( join(self.url, "/checkfees"), - json=payload.dict(), + json=payload.model_dump(), ) self.raise_on_error(resp) return_dict = resp.json() - return CheckFeesResponse_deprecated.parse_obj(return_dict) + return CheckFeesResponse_deprecated.model_validate(return_dict) diff --git a/poetry.lock b/poetry.lock index 42edf8d4..93c47e5b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -18,6 +18,20 @@ typing_extensions = ">=4.0" dev = ["attribution (==1.7.0)", "black (==24.2.0)", "coverage[toml] (==7.4.1)", "flake8 (==7.0.0)", "flake8-bugbear (==24.2.6)", "flit (==3.9.0)", "mypy (==1.8.0)", "ufmt (==2.3.0)", "usort (==1.0.8.post1)"] docs = ["sphinx (==7.2.6)", "sphinx-mdinclude (==0.5.3)"] +[[package]] +name = "annotated-types" +version = "0.7.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +files = [ + {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, + {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.9\""} + [[package]] name = "anyio" version = "3.7.1" @@ -1110,62 +1124,126 @@ files = [ [[package]] name = "pydantic" -version = "1.10.17" -description = "Data validation and settings management using python type hints" +version = "2.8.2" +description = "Data validation using Python type hints" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pydantic-1.10.17-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0fa51175313cc30097660b10eec8ca55ed08bfa07acbfe02f7a42f6c242e9a4b"}, - {file = "pydantic-1.10.17-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c7e8988bb16988890c985bd2093df9dd731bfb9d5e0860db054c23034fab8f7a"}, - {file = "pydantic-1.10.17-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:371dcf1831f87c9e217e2b6a0c66842879a14873114ebb9d0861ab22e3b5bb1e"}, - {file = "pydantic-1.10.17-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4866a1579c0c3ca2c40575398a24d805d4db6cb353ee74df75ddeee3c657f9a7"}, - {file = "pydantic-1.10.17-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:543da3c6914795b37785703ffc74ba4d660418620cc273490d42c53949eeeca6"}, - {file = "pydantic-1.10.17-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7623b59876f49e61c2e283551cc3647616d2fbdc0b4d36d3d638aae8547ea681"}, - {file = "pydantic-1.10.17-cp310-cp310-win_amd64.whl", hash = "sha256:409b2b36d7d7d19cd8310b97a4ce6b1755ef8bd45b9a2ec5ec2b124db0a0d8f3"}, - {file = "pydantic-1.10.17-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fa43f362b46741df8f201bf3e7dff3569fa92069bcc7b4a740dea3602e27ab7a"}, - {file = "pydantic-1.10.17-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2a72d2a5ff86a3075ed81ca031eac86923d44bc5d42e719d585a8eb547bf0c9b"}, - {file = "pydantic-1.10.17-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4ad32aed3bf5eea5ca5decc3d1bbc3d0ec5d4fbcd72a03cdad849458decbc63"}, - {file = "pydantic-1.10.17-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aeb4e741782e236ee7dc1fb11ad94dc56aabaf02d21df0e79e0c21fe07c95741"}, - {file = "pydantic-1.10.17-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d2f89a719411cb234105735a520b7c077158a81e0fe1cb05a79c01fc5eb59d3c"}, - {file = "pydantic-1.10.17-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db3b48d9283d80a314f7a682f7acae8422386de659fffaba454b77a083c3937d"}, - {file = "pydantic-1.10.17-cp311-cp311-win_amd64.whl", hash = "sha256:9c803a5113cfab7bbb912f75faa4fc1e4acff43e452c82560349fff64f852e1b"}, - {file = "pydantic-1.10.17-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:820ae12a390c9cbb26bb44913c87fa2ff431a029a785642c1ff11fed0a095fcb"}, - {file = "pydantic-1.10.17-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c1e51d1af306641b7d1574d6d3307eaa10a4991542ca324f0feb134fee259815"}, - {file = "pydantic-1.10.17-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e53fb834aae96e7b0dadd6e92c66e7dd9cdf08965340ed04c16813102a47fab"}, - {file = "pydantic-1.10.17-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e2495309b1266e81d259a570dd199916ff34f7f51f1b549a0d37a6d9b17b4dc"}, - {file = "pydantic-1.10.17-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:098ad8de840c92ea586bf8efd9e2e90c6339d33ab5c1cfbb85be66e4ecf8213f"}, - {file = "pydantic-1.10.17-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:525bbef620dac93c430d5d6bdbc91bdb5521698d434adf4434a7ef6ffd5c4b7f"}, - {file = "pydantic-1.10.17-cp312-cp312-win_amd64.whl", hash = "sha256:6654028d1144df451e1da69a670083c27117d493f16cf83da81e1e50edce72ad"}, - {file = "pydantic-1.10.17-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c87cedb4680d1614f1d59d13fea353faf3afd41ba5c906a266f3f2e8c245d655"}, - {file = "pydantic-1.10.17-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11289fa895bcbc8f18704efa1d8020bb9a86314da435348f59745473eb042e6b"}, - {file = "pydantic-1.10.17-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94833612d6fd18b57c359a127cbfd932d9150c1b72fea7c86ab58c2a77edd7c7"}, - {file = "pydantic-1.10.17-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:d4ecb515fa7cb0e46e163ecd9d52f9147ba57bc3633dca0e586cdb7a232db9e3"}, - {file = "pydantic-1.10.17-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7017971ffa7fd7808146880aa41b266e06c1e6e12261768a28b8b41ba55c8076"}, - {file = "pydantic-1.10.17-cp37-cp37m-win_amd64.whl", hash = "sha256:e840e6b2026920fc3f250ea8ebfdedf6ea7a25b77bf04c6576178e681942ae0f"}, - {file = "pydantic-1.10.17-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bfbb18b616abc4df70591b8c1ff1b3eabd234ddcddb86b7cac82657ab9017e33"}, - {file = "pydantic-1.10.17-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ebb249096d873593e014535ab07145498957091aa6ae92759a32d40cb9998e2e"}, - {file = "pydantic-1.10.17-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8c209af63ccd7b22fba94b9024e8b7fd07feffee0001efae50dd99316b27768"}, - {file = "pydantic-1.10.17-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d4b40c9e13a0b61583e5599e7950490c700297b4a375b55b2b592774332798b7"}, - {file = "pydantic-1.10.17-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c31d281c7485223caf6474fc2b7cf21456289dbaa31401844069b77160cab9c7"}, - {file = "pydantic-1.10.17-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ae5184e99a060a5c80010a2d53c99aee76a3b0ad683d493e5f0620b5d86eeb75"}, - {file = "pydantic-1.10.17-cp38-cp38-win_amd64.whl", hash = "sha256:ad1e33dc6b9787a6f0f3fd132859aa75626528b49cc1f9e429cdacb2608ad5f0"}, - {file = "pydantic-1.10.17-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7e17c0ee7192e54a10943f245dc79e36d9fe282418ea05b886e1c666063a7b54"}, - {file = "pydantic-1.10.17-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cafb9c938f61d1b182dfc7d44a7021326547b7b9cf695db5b68ec7b590214773"}, - {file = "pydantic-1.10.17-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95ef534e3c22e5abbdbdd6f66b6ea9dac3ca3e34c5c632894f8625d13d084cbe"}, - {file = "pydantic-1.10.17-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62d96b8799ae3d782df7ec9615cb59fc32c32e1ed6afa1b231b0595f6516e8ab"}, - {file = "pydantic-1.10.17-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ab2f976336808fd5d539fdc26eb51f9aafc1f4b638e212ef6b6f05e753c8011d"}, - {file = "pydantic-1.10.17-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b8ad363330557beac73159acfbeed220d5f1bfcd6b930302a987a375e02f74fd"}, - {file = "pydantic-1.10.17-cp39-cp39-win_amd64.whl", hash = "sha256:48db882e48575ce4b39659558b2f9f37c25b8d348e37a2b4e32971dd5a7d6227"}, - {file = "pydantic-1.10.17-py3-none-any.whl", hash = "sha256:e41b5b973e5c64f674b3b4720286ded184dcc26a691dd55f34391c62c6934688"}, - {file = "pydantic-1.10.17.tar.gz", hash = "sha256:f434160fb14b353caf634149baaf847206406471ba70e64657c1e8330277a991"}, + {file = "pydantic-2.8.2-py3-none-any.whl", hash = "sha256:73ee9fddd406dc318b885c7a2eab8a6472b68b8fb5ba8150949fc3db939f23c8"}, + {file = "pydantic-2.8.2.tar.gz", hash = "sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a"}, ] [package.dependencies] -typing-extensions = ">=4.2.0" +annotated-types = ">=0.4.0" +pydantic-core = "2.20.1" +typing-extensions = [ + {version = ">=4.12.2", markers = "python_version >= \"3.13\""}, + {version = ">=4.6.1", markers = "python_version < \"3.13\""}, +] [package.extras] -dotenv = ["python-dotenv (>=0.10.4)"] -email = ["email-validator (>=1.0.3)"] +email = ["email-validator (>=2.0.0)"] + +[[package]] +name = "pydantic-core" +version = "2.20.1" +description = "Core functionality for Pydantic validation and serialization" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic_core-2.20.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3acae97ffd19bf091c72df4d726d552c473f3576409b2a7ca36b2f535ffff4a3"}, + {file = "pydantic_core-2.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41f4c96227a67a013e7de5ff8f20fb496ce573893b7f4f2707d065907bffdbd6"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f239eb799a2081495ea659d8d4a43a8f42cd1fe9ff2e7e436295c38a10c286a"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53e431da3fc53360db73eedf6f7124d1076e1b4ee4276b36fb25514544ceb4a3"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1f62b2413c3a0e846c3b838b2ecd6c7a19ec6793b2a522745b0869e37ab5bc1"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d41e6daee2813ecceea8eda38062d69e280b39df793f5a942fa515b8ed67953"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d482efec8b7dc6bfaedc0f166b2ce349df0011f5d2f1f25537ced4cfc34fd98"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e93e1a4b4b33daed65d781a57a522ff153dcf748dee70b40c7258c5861e1768a"}, + {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7c4ea22b6739b162c9ecaaa41d718dfad48a244909fe7ef4b54c0b530effc5a"}, + {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4f2790949cf385d985a31984907fecb3896999329103df4e4983a4a41e13e840"}, + {file = "pydantic_core-2.20.1-cp310-none-win32.whl", hash = "sha256:5e999ba8dd90e93d57410c5e67ebb67ffcaadcea0ad973240fdfd3a135506250"}, + {file = "pydantic_core-2.20.1-cp310-none-win_amd64.whl", hash = "sha256:512ecfbefef6dac7bc5eaaf46177b2de58cdf7acac8793fe033b24ece0b9566c"}, + {file = "pydantic_core-2.20.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d2a8fa9d6d6f891f3deec72f5cc668e6f66b188ab14bb1ab52422fe8e644f312"}, + {file = "pydantic_core-2.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:175873691124f3d0da55aeea1d90660a6ea7a3cfea137c38afa0a5ffabe37b88"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37eee5b638f0e0dcd18d21f59b679686bbd18917b87db0193ae36f9c23c355fc"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25e9185e2d06c16ee438ed39bf62935ec436474a6ac4f9358524220f1b236e43"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:150906b40ff188a3260cbee25380e7494ee85048584998c1e66df0c7a11c17a6"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ad4aeb3e9a97286573c03df758fc7627aecdd02f1da04516a86dc159bf70121"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3f3ed29cd9f978c604708511a1f9c2fdcb6c38b9aae36a51905b8811ee5cbf1"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0dae11d8f5ded51699c74d9548dcc5938e0804cc8298ec0aa0da95c21fff57b"}, + {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:faa6b09ee09433b87992fb5a2859efd1c264ddc37280d2dd5db502126d0e7f27"}, + {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9dc1b507c12eb0481d071f3c1808f0529ad41dc415d0ca11f7ebfc666e66a18b"}, + {file = "pydantic_core-2.20.1-cp311-none-win32.whl", hash = "sha256:fa2fddcb7107e0d1808086ca306dcade7df60a13a6c347a7acf1ec139aa6789a"}, + {file = "pydantic_core-2.20.1-cp311-none-win_amd64.whl", hash = "sha256:40a783fb7ee353c50bd3853e626f15677ea527ae556429453685ae32280c19c2"}, + {file = "pydantic_core-2.20.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:595ba5be69b35777474fa07f80fc260ea71255656191adb22a8c53aba4479231"}, + {file = "pydantic_core-2.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a4f55095ad087474999ee28d3398bae183a66be4823f753cd7d67dd0153427c9"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9aa05d09ecf4c75157197f27cdc9cfaeb7c5f15021c6373932bf3e124af029f"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e97fdf088d4b31ff4ba35db26d9cc472ac7ef4a2ff2badeabf8d727b3377fc52"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc633a9fe1eb87e250b5c57d389cf28998e4292336926b0b6cdaee353f89a237"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d573faf8eb7e6b1cbbcb4f5b247c60ca8be39fe2c674495df0eb4318303137fe"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26dc97754b57d2fd00ac2b24dfa341abffc380b823211994c4efac7f13b9e90e"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:33499e85e739a4b60c9dac710c20a08dc73cb3240c9a0e22325e671b27b70d24"}, + {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bebb4d6715c814597f85297c332297c6ce81e29436125ca59d1159b07f423eb1"}, + {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:516d9227919612425c8ef1c9b869bbbee249bc91912c8aaffb66116c0b447ebd"}, + {file = "pydantic_core-2.20.1-cp312-none-win32.whl", hash = "sha256:469f29f9093c9d834432034d33f5fe45699e664f12a13bf38c04967ce233d688"}, + {file = "pydantic_core-2.20.1-cp312-none-win_amd64.whl", hash = "sha256:035ede2e16da7281041f0e626459bcae33ed998cca6a0a007a5ebb73414ac72d"}, + {file = "pydantic_core-2.20.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:0827505a5c87e8aa285dc31e9ec7f4a17c81a813d45f70b1d9164e03a813a686"}, + {file = "pydantic_core-2.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:19c0fa39fa154e7e0b7f82f88ef85faa2a4c23cc65aae2f5aea625e3c13c735a"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa223cd1e36b642092c326d694d8bf59b71ddddc94cdb752bbbb1c5c91d833b"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c336a6d235522a62fef872c6295a42ecb0c4e1d0f1a3e500fe949415761b8a19"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7eb6a0587eded33aeefea9f916899d42b1799b7b14b8f8ff2753c0ac1741edac"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:70c8daf4faca8da5a6d655f9af86faf6ec2e1768f4b8b9d0226c02f3d6209703"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9fa4c9bf273ca41f940bceb86922a7667cd5bf90e95dbb157cbb8441008482c"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:11b71d67b4725e7e2a9f6e9c0ac1239bbc0c48cce3dc59f98635efc57d6dac83"}, + {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:270755f15174fb983890c49881e93f8f1b80f0b5e3a3cc1394a255706cabd203"}, + {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c81131869240e3e568916ef4c307f8b99583efaa60a8112ef27a366eefba8ef0"}, + {file = "pydantic_core-2.20.1-cp313-none-win32.whl", hash = "sha256:b91ced227c41aa29c672814f50dbb05ec93536abf8f43cd14ec9521ea09afe4e"}, + {file = "pydantic_core-2.20.1-cp313-none-win_amd64.whl", hash = "sha256:65db0f2eefcaad1a3950f498aabb4875c8890438bc80b19362cf633b87a8ab20"}, + {file = "pydantic_core-2.20.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:4745f4ac52cc6686390c40eaa01d48b18997cb130833154801a442323cc78f91"}, + {file = "pydantic_core-2.20.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a8ad4c766d3f33ba8fd692f9aa297c9058970530a32c728a2c4bfd2616d3358b"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41e81317dd6a0127cabce83c0c9c3fbecceae981c8391e6f1dec88a77c8a569a"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04024d270cf63f586ad41fff13fde4311c4fc13ea74676962c876d9577bcc78f"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eaad4ff2de1c3823fddf82f41121bdf453d922e9a238642b1dedb33c4e4f98ad"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26ab812fa0c845df815e506be30337e2df27e88399b985d0bb4e3ecfe72df31c"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c5ebac750d9d5f2706654c638c041635c385596caf68f81342011ddfa1e5598"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2aafc5a503855ea5885559eae883978c9b6d8c8993d67766ee73d82e841300dd"}, + {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4868f6bd7c9d98904b748a2653031fc9c2f85b6237009d475b1008bfaeb0a5aa"}, + {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa2f457b4af386254372dfa78a2eda2563680d982422641a85f271c859df1987"}, + {file = "pydantic_core-2.20.1-cp38-none-win32.whl", hash = "sha256:225b67a1f6d602de0ce7f6c1c3ae89a4aa25d3de9be857999e9124f15dab486a"}, + {file = "pydantic_core-2.20.1-cp38-none-win_amd64.whl", hash = "sha256:6b507132dcfc0dea440cce23ee2182c0ce7aba7054576efc65634f080dbe9434"}, + {file = "pydantic_core-2.20.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b03f7941783b4c4a26051846dea594628b38f6940a2fdc0df00b221aed39314c"}, + {file = "pydantic_core-2.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1eedfeb6089ed3fad42e81a67755846ad4dcc14d73698c120a82e4ccf0f1f9f6"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:635fee4e041ab9c479e31edda27fcf966ea9614fff1317e280d99eb3e5ab6fe2"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:77bf3ac639c1ff567ae3b47f8d4cc3dc20f9966a2a6dd2311dcc055d3d04fb8a"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ed1b0132f24beeec5a78b67d9388656d03e6a7c837394f99257e2d55b461611"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6514f963b023aeee506678a1cf821fe31159b925c4b76fe2afa94cc70b3222b"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10d4204d8ca33146e761c79f83cc861df20e7ae9f6487ca290a97702daf56006"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2d036c7187b9422ae5b262badb87a20a49eb6c5238b2004e96d4da1231badef1"}, + {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9ebfef07dbe1d93efb94b4700f2d278494e9162565a54f124c404a5656d7ff09"}, + {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6b9d9bb600328a1ce523ab4f454859e9d439150abb0906c5a1983c146580ebab"}, + {file = "pydantic_core-2.20.1-cp39-none-win32.whl", hash = "sha256:784c1214cb6dd1e3b15dd8b91b9a53852aed16671cc3fbe4786f4f1db07089e2"}, + {file = "pydantic_core-2.20.1-cp39-none-win_amd64.whl", hash = "sha256:d2fe69c5434391727efa54b47a1e7986bb0186e72a41b203df8f5b0a19a4f669"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a45f84b09ac9c3d35dfcf6a27fd0634d30d183205230a0ebe8373a0e8cfa0906"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d02a72df14dfdbaf228424573a07af10637bd490f0901cee872c4f434a735b94"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2b27e6af28f07e2f195552b37d7d66b150adbaa39a6d327766ffd695799780f"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:084659fac3c83fd674596612aeff6041a18402f1e1bc19ca39e417d554468482"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:242b8feb3c493ab78be289c034a1f659e8826e2233786e36f2893a950a719bb6"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:38cf1c40a921d05c5edc61a785c0ddb4bed67827069f535d794ce6bcded919fc"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e0bbdd76ce9aa5d4209d65f2b27fc6e5ef1312ae6c5333c26db3f5ade53a1e99"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:254ec27fdb5b1ee60684f91683be95e5133c994cc54e86a0b0963afa25c8f8a6"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:407653af5617f0757261ae249d3fba09504d7a71ab36ac057c938572d1bc9331"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:c693e916709c2465b02ca0ad7b387c4f8423d1db7b4649c551f27a529181c5ad"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b5ff4911aea936a47d9376fd3ab17e970cc543d1b68921886e7f64bd28308d1"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:177f55a886d74f1808763976ac4efd29b7ed15c69f4d838bbd74d9d09cf6fa86"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:964faa8a861d2664f0c7ab0c181af0bea66098b1919439815ca8803ef136fc4e"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:4dd484681c15e6b9a977c785a345d3e378d72678fd5f1f3c0509608da24f2ac0"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f6d6cff3538391e8486a431569b77921adfcdef14eb18fbf19b7c0a5294d4e6a"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a6d511cc297ff0883bc3708b465ff82d7560193169a8b93260f74ecb0a5e08a7"}, + {file = "pydantic_core-2.20.1.tar.gz", hash = "sha256:26ca695eeee5f9f1aeeb211ffc12f10bcb6f71e2989988fda61dabd65db878d4"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" [[package]] name = "pyinstrument" @@ -1885,4 +1963,4 @@ test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", [metadata] lock-version = "2.0" python-versions = "^3.8.1" -content-hash = "d312a7a7c367a8c7f3664f4691e8e39aae6bc9aaf2e116abaf9e7fa1f28fe479" +content-hash = "9d8c5e8091867fb72220928139e7217b499a1448f300627b6336d9c7ceb1d132" diff --git a/pyproject.toml b/pyproject.toml index ca700550..e60789ee 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ license = "MIT" python = "^3.8.1" SQLAlchemy = {version = "1.4.52", extras = ["asyncio"]} click = "^8.1.7" -pydantic = "^1.10.2" +pydantic = "^2.8.2" bech32 = "^1.2.0" fastapi = "^0.104.1" environs = "^9.5.0" diff --git a/tests/test_core.py b/tests/test_core.py index ec2a399d..941e2c3c 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -130,7 +130,7 @@ def test_tokenv3_serialize_example_token_nut00(): ], "memo": "Thank you.", } - tokenObj = TokenV3.parse_obj(token_dict) + tokenObj = TokenV3.model_validate(token_dict) assert ( tokenObj.serialize() == "cashuAeyJ0b2tlbiI6IFt7InByb29mcyI6IFt7ImlkIjogIjliYjlkNTgzOTJjZDg" @@ -192,7 +192,7 @@ def test_tokenv4_serialize_example_single_keyset_nut00(): "m": "http://localhost:3338", "u": "sat", } - tokenObj = TokenV4.parse_obj(token_dict) + tokenObj = TokenV4.model_validate(token_dict) assert ( tokenObj.serialize() == "cashuBpGF0gaJhaUgArSaMTR9YJmFwgaNhYQFhc3hAOWE2ZGJiODQ3YmQyMzJiYTc2ZGIwZGYxOTcyMTZiMjlkM2I4Y2MxNDU1M2NkMjc4MjdmYzFjYzk0MmZlZGI0ZWFjWCEDhhhUP_trhpXfStS6vN6So0qWvc2X3O4NfM-Y1HISZ5JhZGlUaGFuayB5b3VhbXVodHRwOi8vbG9jYWxob3N0OjMzMzhhdWNzYXQ=" @@ -237,7 +237,7 @@ def test_tokenv4_serialize_example_token_nut00(): "m": "http://localhost:3338", "u": "sat", } - tokenObj = TokenV4.parse_obj(token_dict) + tokenObj = TokenV4.model_validate(token_dict) assert ( tokenObj.serialize() diff --git a/tests/test_crypto.py b/tests/test_crypto.py index 5018e5cf..28743204 100644 --- a/tests/test_crypto.py +++ b/tests/test_crypto.py @@ -311,7 +311,7 @@ def test_dleq_carol_on_proof(): ), raw=True, ) - proof = Proof.parse_obj( + proof = Proof.model_validate( { "amount": 1, "id": "00882760bfa2eb41", diff --git a/tests/test_mint_api.py b/tests/test_mint_api.py index e1f69e29..57b9db40 100644 --- a/tests/test_mint_api.py +++ b/tests/test_mint_api.py @@ -47,7 +47,7 @@ async def test_info(ledger: Ledger): info = GetInfoResponse(**response.json()) assert info.nuts assert info.nuts[4]["disabled"] is False - setting = MintMeltMethodSetting.parse_obj(info.nuts[4]["methods"][0]) + setting = MintMeltMethodSetting.model_validate(info.nuts[4]["methods"][0]) assert setting.method == "bolt11" assert setting.unit == "sat" @@ -160,7 +160,7 @@ async def test_split(ledger: Ledger, wallet: Wallet): outputs, rs = wallet._construct_outputs([32, 32], secrets, rs) # outputs = wallet._construct_outputs([32, 32], ["a", "b"], ["c", "d"]) inputs_payload = [p.to_dict() for p in wallet.proofs] - outputs_payload = [o.dict() for o in outputs] + outputs_payload = [o.model_dump() for o in outputs] payload = {"inputs": inputs_payload, "outputs": outputs_payload} response = httpx.post(f"{BASE_URL}/v1/swap", json=payload, timeout=None) assert response.status_code == 200, f"{response.url} {response.status_code}" @@ -238,7 +238,7 @@ async def test_mint(ledger: Ledger, wallet: Wallet): quote_id = invoice.id secrets, rs, derivation_paths = await wallet.generate_secrets_from_to(10000, 10001) outputs, rs = wallet._construct_outputs([32, 32], secrets, rs) - outputs_payload = [o.dict() for o in outputs] + outputs_payload = [o.model_dump() for o in outputs] response = httpx.post( f"{BASE_URL}/v1/mint/bolt11", json={"quote": quote_id, "outputs": outputs_payload}, @@ -369,7 +369,7 @@ async def test_melt_internal(ledger: Ledger, wallet: Wallet): # outputs for change secrets, rs, derivation_paths = await wallet.generate_n_secrets(1) outputs, rs = wallet._construct_outputs([2], secrets, rs) - outputs_payload = [o.dict() for o in outputs] + outputs_payload = [o.model_dump() for o in outputs] response = httpx.post( f"{BASE_URL}/v1/melt/bolt11", @@ -428,7 +428,7 @@ async def test_melt_external(ledger: Ledger, wallet: Wallet): # outputs for change secrets, rs, derivation_paths = await wallet.generate_n_secrets(1) outputs, rs = wallet._construct_outputs([2], secrets, rs) - outputs_payload = [o.dict() for o in outputs] + outputs_payload = [o.model_dump() for o in outputs] response = httpx.post( f"{BASE_URL}/v1/melt/bolt11", @@ -471,10 +471,10 @@ async def test_api_check_state(ledger: Ledger): payload = PostCheckStateRequest(Ys=["asdasdasd", "asdasdasd1"]) response = httpx.post( f"{BASE_URL}/v1/checkstate", - json=payload.dict(), + json=payload.model_dump(), ) assert response.status_code == 200, f"{response.url} {response.status_code}" - response = PostCheckStateResponse.parse_obj(response.json()) + response = PostCheckStateResponse.model_validate(response.json()) assert response assert len(response.states) == 2 assert response.states[0].state == ProofSpentState.unspent @@ -501,13 +501,13 @@ async def test_api_restore(ledger: Ledger, wallet: Wallet): payload = PostRestoreRequest(outputs=outputs) response = httpx.post( f"{BASE_URL}/v1/restore", - json=payload.dict(), + json=payload.model_dump(), ) data = response.json() assert "signatures" in data assert "outputs" in data assert response.status_code == 200, f"{response.url} {response.status_code}" - response = PostRestoreResponse.parse_obj(response.json()) + response = PostRestoreResponse.model_validate(response.json()) assert response assert response assert len(response.signatures) == 1 diff --git a/tests/test_mint_api_deprecated.py b/tests/test_mint_api_deprecated.py index b9aac81a..6e1caee6 100644 --- a/tests/test_mint_api_deprecated.py +++ b/tests/test_mint_api_deprecated.py @@ -74,7 +74,7 @@ async def test_split(ledger: Ledger, wallet: Wallet): outputs, rs = wallet._construct_outputs([32, 32], secrets, rs) # outputs = wallet._construct_outputs([32, 32], ["a", "b"], ["c", "d"]) inputs_payload = [p.to_dict() for p in wallet.proofs] - outputs_payload = [o.dict() for o in outputs] + outputs_payload = [o.model_dump() for o in outputs] # strip "id" from outputs_payload, which is not used in the deprecated split endpoint for o in outputs_payload: o.pop("id") @@ -95,7 +95,7 @@ async def test_split_deprecated_with_amount(ledger: Ledger, wallet: Wallet): outputs, rs = wallet._construct_outputs([32, 32], secrets, rs) # outputs = wallet._construct_outputs([32, 32], ["a", "b"], ["c", "d"]) inputs_payload = [p.to_dict() for p in wallet.proofs] - outputs_payload = [o.dict() for o in outputs] + outputs_payload = [o.model_dump() for o in outputs] # strip "id" from outputs_payload, which is not used in the deprecated split endpoint for o in outputs_payload: o.pop("id") @@ -128,7 +128,7 @@ async def test_mint(ledger: Ledger, wallet: Wallet): quote_id = invoice.id secrets, rs, derivation_paths = await wallet.generate_secrets_from_to(10000, 10001) outputs, rs = wallet._construct_outputs([32, 32], secrets, rs) - outputs_payload = [o.dict() for o in outputs] + outputs_payload = [o.model_dump() for o in outputs] response = httpx.post( f"{BASE_URL}/mint", json={"outputs": outputs_payload}, @@ -168,7 +168,7 @@ async def test_melt_internal(ledger: Ledger, wallet: Wallet): # outputs for change secrets, rs, derivation_paths = await wallet.generate_n_secrets(1) outputs, rs = wallet._construct_outputs([2], secrets, rs) - outputs_payload = [o.dict() for o in outputs] + outputs_payload = [o.model_dump() for o in outputs] response = httpx.post( f"{BASE_URL}/melt", @@ -247,7 +247,7 @@ async def test_melt_external(ledger: Ledger, wallet: Wallet): # outputs for change secrets, rs, derivation_paths = await wallet.generate_n_secrets(1) outputs, rs = wallet._construct_outputs([2], secrets, rs) - outputs_payload = [o.dict() for o in outputs] + outputs_payload = [o.model_dump() for o in outputs] response = httpx.post( f"{BASE_URL}/melt", @@ -310,10 +310,10 @@ async def test_api_check_state(ledger: Ledger): payload = CheckSpendableRequest_deprecated(proofs=proofs) response = httpx.post( f"{BASE_URL}/check", - json=payload.dict(), + json=payload.model_dump(), ) assert response.status_code == 200, f"{response.url} {response.status_code}" - states = CheckSpendableResponse_deprecated.parse_obj(response.json()) + states = CheckSpendableResponse_deprecated.model_validate(response.json()) assert states.spendable assert len(states.spendable) == 2 assert states.pending @@ -337,13 +337,13 @@ async def test_api_restore(ledger: Ledger, wallet: Wallet): payload = PostRestoreRequest(outputs=outputs) response = httpx.post( f"{BASE_URL}/restore", - json=payload.dict(), + json=payload.model_dump(), ) data = response.json() assert "promises" in data assert "outputs" in data assert response.status_code == 200, f"{response.url} {response.status_code}" - response = PostRestoreResponse.parse_obj(response.json()) + response = PostRestoreResponse.model_validate(response.json()) assert response assert response.promises assert len(response.promises) == 1 diff --git a/tests/test_wallet_api.py b/tests/test_wallet_api.py index 14602f2e..d42f233e 100644 --- a/tests/test_wallet_api.py +++ b/tests/test_wallet_api.py @@ -28,14 +28,14 @@ async def test_invoice(wallet: Wallet): with TestClient(app) as client: response = client.post("/lightning/create_invoice?amount=100") assert response.status_code == 200 - invoice_response = InvoiceResponse.parse_obj(response.json()) + invoice_response = InvoiceResponse.model_validate(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()) + state = PaymentStatus.model_validate(response2.json()) await asyncio.sleep(0.1) print("state:", state) print("paid") @@ -170,14 +170,14 @@ async def test_flow(wallet: Wallet): response = client.get("/balance") initial_balance = response.json()["balance"] response = client.post("/lightning/create_invoice?amount=100") - invoice_response = InvoiceResponse.parse_obj(response.json()) + invoice_response = InvoiceResponse.model_validate(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()) + state = PaymentStatus.model_validate(response2.json()) await asyncio.sleep(0.1) print("state:", state) diff --git a/tests/test_wallet_subscription.py b/tests/test_wallet_subscription.py index ce77a16f..773d8484 100644 --- a/tests/test_wallet_subscription.py +++ b/tests/test_wallet_subscription.py @@ -99,17 +99,17 @@ def callback(msg: JSONRPCNotficationParams): # the first one is the UNSPENT state pending_stack = msg_stack[:n_subscriptions] for msg in pending_stack: - proof_state = ProofState.parse_obj(msg.payload) + proof_state = ProofState.model_validate(msg.payload) assert proof_state.state == ProofSpentState.unspent # the second one is the PENDING state spent_stack = msg_stack[n_subscriptions : n_subscriptions * 2] for msg in spent_stack: - proof_state = ProofState.parse_obj(msg.payload) + proof_state = ProofState.model_validate(msg.payload) assert proof_state.state == ProofSpentState.pending # the third one is the SPENT state spent_stack = msg_stack[n_subscriptions * 2 :] for msg in spent_stack: - proof_state = ProofState.parse_obj(msg.payload) + proof_state = ProofState.model_validate(msg.payload) assert proof_state.state == ProofSpentState.spent From 3f74256d518be996263c26198e98ea2f8603ae18 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 10 Jul 2024 01:54:05 +0200 Subject: [PATCH 2/2] root --- cashu/core/models.py | 2 +- cashu/core/settings.py | 2 +- cashu/mint/router_deprecated.py | 4 ++-- cashu/wallet/api/router.py | 5 +---- 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/cashu/core/models.py b/cashu/core/models.py index 6feda5d5..d39dfa78 100644 --- a/cashu/core/models.py +++ b/cashu/core/models.py @@ -82,7 +82,7 @@ class KeysetsResponse(BaseModel): class KeysResponse_deprecated(RootModel[Dict[str, str]]): - pass + root: Dict[str, str] = {} class KeysetsResponse_deprecated(BaseModel): diff --git a/cashu/core/settings.py b/cashu/core/settings.py index fc634c68..5a080f56 100644 --- a/cashu/core/settings.py +++ b/cashu/core/settings.py @@ -155,7 +155,7 @@ class WalletSettings(CashuSettings): socks_port: Optional[int] = Field(default=9050) # deprecated socks_proxy: Optional[str] = Field(default=None) http_proxy: Optional[str] = Field(default=None) - mint_url: Optional[str] = Field(default=None) + mint_url: str = Field(default=None) mint_host: str = Field(default="localhost") mint_port: int = Field(default=3338) wallet_name: str = Field(default="wallet") diff --git a/cashu/mint/router_deprecated.py b/cashu/mint/router_deprecated.py index 63813a8c..1af212ef 100644 --- a/cashu/mint/router_deprecated.py +++ b/cashu/mint/router_deprecated.py @@ -76,7 +76,7 @@ async def keys_deprecated() -> Dict[str, str]: logger.trace("> GET /keys") keyset = ledger.get_keyset() keys = KeysResponse_deprecated.model_validate(keyset) - return keys.__root__ + return keys.root @router_deprecated.get( @@ -100,7 +100,7 @@ async def keyset_deprecated(idBase64Urlsafe: str) -> Dict[str, str]: id = idBase64Urlsafe.replace("-", "+").replace("_", "/") keyset = ledger.get_keyset(keyset_id=id) keys = KeysResponse_deprecated.model_validate(keyset) - return keys.__root__ + return keys.root @router_deprecated.get( diff --git a/cashu/wallet/api/router.py b/cashu/wallet/api/router.py index 46b1330b..b8029c5a 100644 --- a/cashu/wallet/api/router.py +++ b/cashu/wallet/api/router.py @@ -52,11 +52,8 @@ async def mint_wallet( mint_url: Optional[str] = None, raise_connection_error: bool = True ) -> LightningWallet: - url = mint_url or settings.mint_url - if not url: - raise Exception("No mint URL provided.") lightning_wallet = await LightningWallet.with_db( - url, + mint_url or settings.mint_url, db=os.path.join(settings.cashu_dir, settings.wallet_name), name=settings.wallet_name, )