diff --git a/pyproject.toml b/pyproject.toml index fb0792f9..c8e31e7f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,15 +30,17 @@ dynamic = [ "version" ] dependencies = [ "aiohttp>=3.8.3", "aioresponses>=0.7.6", - "aleph-message>=0.6", + "aleph-message @ git+https://github.com/aleph-im/aleph-message@108-upgrade-pydantic-version#egg=aleph-message", "aleph-superfluid>=0.2.1", - "base58==2.1.1", # Needed now as default with _load_account changement + "base58==2.1.1", # Needed now as default with _load_account changement "coincurve; python_version<'3.11'", "coincurve>=19; python_version>='3.11'", "eth-abi>=4; python_version>='3.11'", "eth-typing==4.3.1", "jwcrypto==1.5.6", - "pynacl==1.5", # Needed now as default with _load_account changement + "pydantic-core>=2", + "pydantic-settings>=2", + "pynacl==1.5", # Needed now as default with _load_account changement "python-magic", "typing-extensions", "web3==6.3", @@ -61,7 +63,7 @@ optional-dependencies.encryption = [ "eciespy>=0.3.13; python_version>='3.11'", ] optional-dependencies.ledger = [ - "ledgereth==0.10", + "ledgereth==0.9.1", ] optional-dependencies.mqtt = [ "aiomqtt<=0.1.3", @@ -153,6 +155,8 @@ dependencies = [ "ruff==0.4.8", "isort==5.13.2", "pyproject-fmt==2.2.1", + "pydantic-core>=2", + "pydantic-settings>=2", ] [tool.hatch.envs.linting.scripts] typing = "mypy --config-file=pyproject.toml {args:} ./src/ ./tests/ ./examples/" diff --git a/src/aleph/sdk/chains/common.py b/src/aleph/sdk/chains/common.py index 0a90183c..8f57f9b4 100644 --- a/src/aleph/sdk/chains/common.py +++ b/src/aleph/sdk/chains/common.py @@ -72,7 +72,7 @@ async def sign_message(self, message: Dict) -> Dict: """ message = self._setup_sender(message) signature = await self.sign_raw(get_verification_buffer(message)) - message["signature"] = signature.hex() + message["signature"] = "0x" + signature.hex() return message @abstractmethod diff --git a/src/aleph/sdk/chains/remote.py b/src/aleph/sdk/chains/remote.py index 931b68f3..917cf39b 100644 --- a/src/aleph/sdk/chains/remote.py +++ b/src/aleph/sdk/chains/remote.py @@ -52,7 +52,7 @@ async def from_crypto_host( session = aiohttp.ClientSession(connector=connector) async with session.get(f"{host}/properties") as response: - response.raise_for_status() + await response.raise_for_status() data = await response.json() properties = AccountProperties(**data) @@ -75,7 +75,7 @@ def private_key(self): async def sign_message(self, message: Dict) -> Dict: """Sign a message inplace.""" async with self._session.post(f"{self._host}/sign", json=message) as response: - response.raise_for_status() + await response.raise_for_status() return await response.json() async def sign_raw(self, buffer: bytes) -> bytes: diff --git a/src/aleph/sdk/client/authenticated_http.py b/src/aleph/sdk/client/authenticated_http.py index 1d0d69d7..2ae0d0a0 100644 --- a/src/aleph/sdk/client/authenticated_http.py +++ b/src/aleph/sdk/client/authenticated_http.py @@ -251,7 +251,7 @@ async def _broadcast( url = "/api/v0/messages" logger.debug(f"Posting message on {url}") - message_dict = message.dict(include=self.BROADCAST_MESSAGE_FIELDS) + message_dict = message.model_dump(include=self.BROADCAST_MESSAGE_FIELDS) async with self.http_session.post( url, json={ @@ -293,7 +293,7 @@ async def create_post( ) message, status, _ = await self.submit( - content=content.dict(exclude_none=True), + content=content.model_dump(exclude_none=True), message_type=MessageType.post, channel=channel, allow_inlining=inline, @@ -321,7 +321,7 @@ async def create_aggregate( ) message, status, _ = await self.submit( - content=content_.dict(exclude_none=True), + content=content_.model_dump(exclude_none=True), message_type=MessageType.aggregate, channel=channel, allow_inlining=inline, @@ -395,7 +395,7 @@ async def create_store( content = StoreContent.parse_obj(values) message, status, _ = await self.submit( - content=content.dict(exclude_none=True), + content=content.model_dump(exclude_none=True), message_type=MessageType.store, channel=channel, allow_inlining=True, @@ -449,7 +449,7 @@ async def create_program( ) message, status, _ = await self.submit( - content=content.dict(exclude_none=True), + content=content.model_dump(exclude_none=True), message_type=MessageType.program, channel=channel, storage_engine=storage_engine, @@ -573,7 +573,7 @@ async def forget( ) message, status, _ = await self.submit( - content=content.dict(exclude_none=True), + content=content.model_dump(exclude_none=True), message_type=MessageType.forget, channel=channel, storage_engine=storage_engine, @@ -617,11 +617,11 @@ async def _storage_push_file_with_message( # Prepare the STORE message message = await self.generate_signed_message( message_type=MessageType.store, - content=store_content.dict(exclude_none=True), + content=store_content.model_dump(exclude_none=True), channel=channel, ) metadata = { - "message": message.dict(exclude_none=True), + "message": message.model_dump(exclude_none=True), "sync": sync, } data.add_field( @@ -665,7 +665,7 @@ async def _upload_file_native( item_hash=ItemHash(file_hash), mime_type=mime_type, # type: ignore time=time.time(), - **extra_fields, + **(extra_fields or {}), ) message, _ = await self._storage_push_file_with_message( file_content=file_content, diff --git a/src/aleph/sdk/client/http.py b/src/aleph/sdk/client/http.py index f4e8b898..6d2b42c7 100644 --- a/src/aleph/sdk/client/http.py +++ b/src/aleph/sdk/client/http.py @@ -191,7 +191,7 @@ async def get_posts( posts: List[Post] = [] for post_raw in posts_raw: try: - posts.append(Post.parse_obj(post_raw)) + posts.append(Post.model_validate(post_raw)) except ValidationError as e: if not ignore_invalid_messages: raise e diff --git a/src/aleph/sdk/client/vm_confidential_client.py b/src/aleph/sdk/client/vm_confidential_client.py index e027b384..0d9d6e18 100644 --- a/src/aleph/sdk/client/vm_confidential_client.py +++ b/src/aleph/sdk/client/vm_confidential_client.py @@ -105,7 +105,7 @@ async def measurement(self, vm_id: ItemHash) -> SEVMeasurement: status, text = await self.perform_operation( vm_id, "confidential/measurement", method="GET" ) - sev_measurement = SEVMeasurement.parse_raw(text) + sev_measurement = SEVMeasurement.model_validate_json(text) return sev_measurement async def validate_measure( diff --git a/src/aleph/sdk/conf.py b/src/aleph/sdk/conf.py index c925a05e..2d3a03e1 100644 --- a/src/aleph/sdk/conf.py +++ b/src/aleph/sdk/conf.py @@ -3,11 +3,12 @@ import os from pathlib import Path from shutil import which -from typing import Dict, Optional, Union +from typing import ClassVar, Dict, List, Optional, Union from aleph_message.models import Chain from aleph_message.models.execution.environment import HypervisorType -from pydantic import BaseModel, BaseSettings, Field +from pydantic import BaseModel, Field +from pydantic_settings import BaseSettings, SettingsConfigDict from aleph.sdk.types import ChainInfo @@ -41,7 +42,7 @@ class Settings(BaseSettings): REMOTE_CRYPTO_HOST: Optional[str] = None REMOTE_CRYPTO_UNIX_SOCKET: Optional[str] = None ADDRESS_TO_USE: Optional[str] = None - HTTP_REQUEST_TIMEOUT = 15.0 + HTTP_REQUEST_TIMEOUT: ClassVar[float] = 15.0 DEFAULT_CHANNEL: str = "ALEPH-CLOUDSOLUTIONS" @@ -78,14 +79,14 @@ class Settings(BaseSettings): CODE_USES_SQUASHFS: bool = which("mksquashfs") is not None # True if command exists - VM_URL_PATH = "https://aleph.sh/vm/{hash}" - VM_URL_HOST = "https://{hash_base32}.aleph.sh" - IPFS_GATEWAY = "https://ipfs.aleph.cloud/ipfs/" - CRN_URL_FOR_PROGRAMS = "https://dchq.staging.aleph.sh/" + VM_URL_PATH: ClassVar[str] = "https://aleph.sh/vm/{hash}" + VM_URL_HOST: ClassVar[str] = "https://{hash_base32}.aleph.sh" + IPFS_GATEWAY: ClassVar[str] = "https://ipfs.aleph.cloud/ipfs/" + CRN_URL_FOR_PROGRAMS: ClassVar[str] = "https://dchq.staging.aleph.sh/" # Web3Provider settings - TOKEN_DECIMALS = 18 - TX_TIMEOUT = 60 * 3 + TOKEN_DECIMALS: ClassVar[int] = 18 + TX_TIMEOUT: ClassVar[int] = 60 * 3 CHAINS: Dict[Union[Chain, str], ChainInfo] = { # TESTNETS "SEPOLIA": ChainInfo( @@ -214,16 +215,15 @@ class Settings(BaseSettings): DEFAULT_CHAIN: Chain = Chain.ETH # Dns resolver - DNS_IPFS_DOMAIN = "ipfs.public.aleph.sh" - DNS_PROGRAM_DOMAIN = "program.public.aleph.sh" - DNS_INSTANCE_DOMAIN = "instance.public.aleph.sh" - DNS_STATIC_DOMAIN = "static.public.aleph.sh" - DNS_RESOLVERS = ["9.9.9.9", "1.1.1.1"] - - class Config: - env_prefix = "ALEPH_" - case_sensitive = False - env_file = ".env" + DNS_IPFS_DOMAIN: ClassVar[str] = "ipfs.public.aleph.sh" + DNS_PROGRAM_DOMAIN: ClassVar[str] = "program.public.aleph.sh" + DNS_INSTANCE_DOMAIN: ClassVar[str] = "instance.public.aleph.sh" + DNS_STATIC_DOMAIN: ClassVar[str] = "static.public.aleph.sh" + DNS_RESOLVERS: ClassVar[List[str]] = ["9.9.9.9", "1.1.1.1"] + + model_config = SettingsConfigDict( + env_prefix="ALEPH_", case_sensitive=False, env_file=".env" + ) class MainConfiguration(BaseModel): @@ -234,8 +234,7 @@ class MainConfiguration(BaseModel): path: Path chain: Chain - class Config: - use_enum_values = True + model_config = SettingsConfigDict(use_enum_values=True) # Settings singleton @@ -291,7 +290,7 @@ def save_main_configuration(file_path: Path, data: MainConfiguration): Synchronously save a single ChainAccount object as JSON to a file. """ with file_path.open("w") as file: - data_serializable = data.dict() + data_serializable = data.model_dump() data_serializable["path"] = str(data_serializable["path"]) json.dump(data_serializable, file, indent=4) diff --git a/src/aleph/sdk/domain.py b/src/aleph/sdk/domain.py index a8f3fd82..525e6cef 100644 --- a/src/aleph/sdk/domain.py +++ b/src/aleph/sdk/domain.py @@ -52,11 +52,11 @@ def raise_error(self, status: Dict[str, bool]): def hostname_from_url(url: Union[HttpUrl, str]) -> Hostname: """Extract FQDN from url""" - parsed = urlparse(url) + parsed = urlparse(str(url)) if all([parsed.scheme, parsed.netloc]) is True: url = parsed.netloc - return Hostname(url) + return Hostname(str(url)) async def get_target_type(fqdn: Hostname) -> Optional[TargetType]: diff --git a/src/aleph/sdk/query/responses.py b/src/aleph/sdk/query/responses.py index 4b598f12..277a1bea 100644 --- a/src/aleph/sdk/query/responses.py +++ b/src/aleph/sdk/query/responses.py @@ -9,7 +9,7 @@ ItemType, MessageConfirmation, ) -from pydantic import BaseModel, Field +from pydantic import BaseModel, ConfigDict, Field class Post(BaseModel): @@ -48,9 +48,9 @@ class Post(BaseModel): ref: Optional[Union[str, Any]] = Field( description="Other message referenced by this one" ) + address: Optional[str] = Field(description="Address of the sender") - class Config: - allow_extra = False + model_config = ConfigDict(extra="forbid") class PaginationResponse(BaseModel): @@ -64,14 +64,14 @@ class PostsResponse(PaginationResponse): """Response from an aleph.im node API on the path /api/v0/posts.json""" posts: List[Post] - pagination_item = "posts" + pagination_item: str = "posts" class MessagesResponse(PaginationResponse): """Response from an aleph.im node API on the path /api/v0/messages.json""" messages: List[AlephMessage] - pagination_item = "messages" + pagination_item: str = "messages" class PriceResponse(BaseModel): diff --git a/src/aleph/sdk/types.py b/src/aleph/sdk/types.py index 05fa9815..cf23f19d 100644 --- a/src/aleph/sdk/types.py +++ b/src/aleph/sdk/types.py @@ -2,7 +2,7 @@ from enum import Enum from typing import Dict, Optional, Protocol, TypeVar -from pydantic import BaseModel +from pydantic import BaseModel, Field __all__ = ("StorageEnum", "Account", "AccountFromPrivateKey", "GenericMessage") @@ -87,10 +87,10 @@ class StoredContent(BaseModel): A stored content. """ - filename: Optional[str] - hash: Optional[str] - url: Optional[str] - error: Optional[str] + filename: Optional[str] = Field(default=None) + hash: Optional[str] = Field(default=None) + url: Optional[str] = Field(default=None) + error: Optional[str] = Field(default=None) class TokenType(str, Enum): diff --git a/src/aleph/sdk/utils.py b/src/aleph/sdk/utils.py index 5cbc1e8c..1c7c4ee9 100644 --- a/src/aleph/sdk/utils.py +++ b/src/aleph/sdk/utils.py @@ -28,6 +28,7 @@ from uuid import UUID from zipfile import BadZipFile, ZipFile +import pydantic_core from aleph_message.models import ( Chain, InstanceContent, @@ -56,14 +57,13 @@ from aleph_message.models.execution.volume import ( MachineVolume, ParentVolume, - PersistentVolumeSizeMib, + PersistentVolume, VolumePersistence, ) from aleph_message.utils import Mebibytes from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from jwcrypto.jwa import JWA -from pydantic.json import pydantic_encoder from aleph.sdk.conf import settings from aleph.sdk.types import GenericMessage, SEVInfo, SEVMeasurement @@ -202,7 +202,7 @@ def extended_json_encoder(obj: Any) -> Any: elif isinstance(obj, time): return obj.hour * 3600 + obj.minute * 60 + obj.second + obj.microsecond / 1e6 else: - return pydantic_encoder(obj) + return pydantic_core.to_jsonable_python(obj) def parse_volume(volume_dict: Union[Mapping, MachineVolume]) -> MachineVolume: @@ -213,7 +213,7 @@ def parse_volume(volume_dict: Union[Mapping, MachineVolume]) -> MachineVolume: for volume_type in get_args(MachineVolume): try: - return volume_type.parse_obj(volume_dict) + return volume_type.model_validate(volume_dict) except ValueError: pass raise ValueError(f"Could not parse volume: {volume_dict}") @@ -503,7 +503,9 @@ def make_instance_content( ref=ItemHash(rootfs), use_latest=True, ), - size_mib=PersistentVolumeSizeMib(rootfs_size), + size_mib=PersistentVolume.model_validate( + {"size_mib": rootfs_size} + ).size_mib, persistence=VolumePersistence.host, ), volumes=[parse_volume(volume) for volume in volumes], diff --git a/src/aleph/sdk/vm/cache.py b/src/aleph/sdk/vm/cache.py index ff5ca7c8..a7ac6acc 100644 --- a/src/aleph/sdk/vm/cache.py +++ b/src/aleph/sdk/vm/cache.py @@ -70,7 +70,7 @@ def __init__( ) self.cache = {} - self.api_host = connector_url if connector_url else settings.API_HOST + self.api_host = str(connector_url) if connector_url else settings.API_HOST async def get(self, key: str) -> Optional[bytes]: sanitized_key = sanitize_cache_key(key) diff --git a/tests/unit/aleph_vm_authentication.py b/tests/unit/aleph_vm_authentication.py index 6083a119..c1710c16 100644 --- a/tests/unit/aleph_vm_authentication.py +++ b/tests/unit/aleph_vm_authentication.py @@ -1,4 +1,6 @@ # Keep datetime import as is as it allow patching in test +from __future__ import annotations + import datetime import functools import json @@ -13,7 +15,7 @@ from eth_account.messages import encode_defunct from jwcrypto import jwk from jwcrypto.jwa import JWA -from pydantic import BaseModel, ValidationError, root_validator, validator +from pydantic import BaseModel, ValidationError, field_validator, model_validator from aleph.sdk.utils import bytes_from_hex @@ -63,23 +65,21 @@ class SignedPubKeyHeader(BaseModel): signature: bytes payload: bytes - @validator("signature") + @field_validator("signature") def signature_must_be_hex(cls, value: bytes) -> bytes: """Convert the signature from hexadecimal to bytes""" - return bytes_from_hex(value.decode()) - @validator("payload") + @field_validator("payload") def payload_must_be_hex(cls, value: bytes) -> bytes: """Convert the payload from hexadecimal to bytes""" - return bytes_from_hex(value.decode()) - @root_validator(pre=False, skip_on_failure=True) - def check_expiry(cls, values) -> Dict[str, bytes]: + @model_validator(mode="after") # type: ignore + def check_expiry(cls, values: SignedPubKeyHeader) -> SignedPubKeyHeader: """Check that the token has not expired""" - payload: bytes = values["payload"] - content = SignedPubKeyPayload.parse_raw(payload) + payload: bytes = values.payload + content = SignedPubKeyPayload.model_validate_json(payload) if not is_token_still_valid(content.expires): msg = "Token expired" @@ -87,12 +87,11 @@ def check_expiry(cls, values) -> Dict[str, bytes]: return values - @root_validator(pre=False, skip_on_failure=True) - def check_signature(cls, values: Dict[str, bytes]) -> Dict[str, bytes]: - """Check that the signature is valid""" - signature: bytes = values["signature"] - payload: bytes = values["payload"] - content = SignedPubKeyPayload.parse_raw(payload) + @model_validator(mode="after") # type: ignore + def check_signature(cls, values: SignedPubKeyHeader) -> SignedPubKeyHeader: + signature: bytes = values.signature + payload: bytes = values.payload + content = SignedPubKeyPayload.model_validate_json(payload) if not verify_wallet_signature(signature, payload.hex(), content.address): msg = "Invalid signature" @@ -103,7 +102,7 @@ def check_signature(cls, values: Dict[str, bytes]) -> Dict[str, bytes]: @property def content(self) -> SignedPubKeyPayload: """Return the content of the header""" - return SignedPubKeyPayload.parse_raw(self.payload) + return SignedPubKeyPayload.model_validate_json(self.payload) class SignedOperationPayload(BaseModel): @@ -113,7 +112,7 @@ class SignedOperationPayload(BaseModel): path: str # body_sha256: str # disabled since there is no body - @validator("time") + @field_validator("time") def time_is_current(cls, v: datetime.datetime) -> datetime.datetime: """Check that the time is current and the payload is not a replay attack.""" max_past = datetime.datetime.now(tz=datetime.timezone.utc) - datetime.timedelta( @@ -135,7 +134,7 @@ class SignedOperation(BaseModel): signature: bytes payload: bytes - @validator("signature") + @field_validator("signature") def signature_must_be_hex(cls, value: str) -> bytes: """Convert the signature from hexadecimal to bytes""" @@ -147,17 +146,17 @@ def signature_must_be_hex(cls, value: str) -> bytes: logger.warning(value) raise error - @validator("payload") + @field_validator("payload") def payload_must_be_hex(cls, v) -> bytes: """Convert the payload from hexadecimal to bytes""" v = bytes.fromhex(v.decode()) - _ = SignedOperationPayload.parse_raw(v) + _ = SignedOperationPayload.model_validate_json(v) return v @property def content(self) -> SignedOperationPayload: """Return the content of the header""" - return SignedOperationPayload.parse_raw(self.payload) + return SignedOperationPayload.model_validate_json(self.payload) def get_signed_pubkey(request: web.Request) -> SignedPubKeyHeader: @@ -168,7 +167,7 @@ def get_signed_pubkey(request: web.Request) -> SignedPubKeyHeader: raise web.HTTPBadRequest(reason="Missing X-SignedPubKey header") try: - return SignedPubKeyHeader.parse_raw(signed_pubkey_header) + return SignedPubKeyHeader.model_validate_json(signed_pubkey_header) except KeyError as error: logger.debug(f"Missing X-SignedPubKey header: {error}") @@ -199,7 +198,7 @@ def get_signed_operation(request: web.Request) -> SignedOperation: """Get the signed operation public key that is signed by the ephemeral key from the request headers.""" try: signed_operation = request.headers["X-SignedOperation"] - return SignedOperation.parse_raw(signed_operation) + return SignedOperation.model_validate_json(signed_operation) except KeyError as error: raise web.HTTPBadRequest(reason="Missing X-SignedOperation header") from error except json.JSONDecodeError as error: @@ -259,8 +258,8 @@ async def authenticate_websocket_message( message, domain_name: Optional[str] = DOMAIN_NAME ) -> str: """Authenticate a websocket message since JS cannot configure headers on WebSockets.""" - signed_pubkey = SignedPubKeyHeader.parse_obj(message["X-SignedPubKey"]) - signed_operation = SignedOperation.parse_obj(message["X-SignedOperation"]) + signed_pubkey = SignedPubKeyHeader.model_validate(message["X-SignedPubKey"]) + signed_operation = SignedOperation.model_validate(message["X-SignedOperation"]) if signed_operation.content.domain != domain_name: logger.debug( f"Invalid domain '{signed_operation.content.domain}' != '{domain_name}'" diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index c1c56fcd..385d2836 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -71,7 +71,7 @@ def rejected_message(): @pytest.fixture def aleph_messages() -> List[AlephMessage]: return [ - AggregateMessage.parse_obj( + AggregateMessage.model_validate( { "item_hash": "5b26d949fe05e38f535ef990a89da0473f9d700077cced228f2d36e73fca1fd6", "type": "AGGREGATE", @@ -95,7 +95,7 @@ def aleph_messages() -> List[AlephMessage]: "confirmed": False, } ), - PostMessage.parse_obj( + PostMessage.model_validate( { "item_hash": "70f3798fdc68ce0ee03715a5547ee24e2c3e259bf02e3f5d1e4bf5a6f6a5e99f", "type": "POST", @@ -135,7 +135,9 @@ def json_post() -> dict: def raw_messages_response(aleph_messages) -> Callable[[int], Dict[str, Any]]: return lambda page: { "messages": ( - [message.dict() for message in aleph_messages] if int(page) == 1 else [] + [message.model_dump() for message in aleph_messages] + if int(page) == 1 + else [] ), "pagination_item": "messages", "pagination_page": int(page), diff --git a/tests/unit/test_remote_account.py b/tests/unit/test_remote_account.py index cb4a2af5..3abe979e 100644 --- a/tests/unit/test_remote_account.py +++ b/tests/unit/test_remote_account.py @@ -22,7 +22,7 @@ async def test_remote_storage(): curve="secp256k1", address=local_account.get_address(), public_key=local_account.get_public_key(), - ).dict() + ).model_dump() ) remote_account = await RemoteAccount.from_crypto_host( diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index c560455d..4ceb5a3f 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -13,7 +13,6 @@ ProgramMessage, StoreMessage, ) -from aleph_message.models.execution.environment import MachineResources from aleph_message.models.execution.volume import ( EphemeralVolume, ImmutableVolume, @@ -116,15 +115,16 @@ def test_enum_as_str(): ( MessageType.aggregate, { + "address": "0x1", "content": { - "Hello": MachineResources( - vcpus=1, - memory=1024, - seconds=1, - ) + "Hello": { + "vcpus": 1, + "memory": 1024, + "seconds": 1, + "published_ports": None, + }, }, "key": "test", - "address": "0x1", "time": 1.0, }, ), @@ -141,7 +141,7 @@ async def test_prepare_aleph_message( channel="TEST", ) - assert message.content.dict() == content + assert message.content.model_dump() == content def test_parse_immutable_volume(): @@ -219,7 +219,7 @@ def test_compute_confidential_measure(): assert base64.b64encode(tik) == b"npOTEc4mtRGfXfB+G6EBdw==" expected_hash = "d06471f485c0a61aba5a431ec136b947be56907acf6ed96afb11788ae4525aeb" nonce = base64.b64decode("URQNqJAqh/2ep4drjx/XvA==") - sev_info = SEVInfo.parse_obj( + sev_info = SEVInfo.model_validate( { "enabled": True, "api_major": 1, diff --git a/tests/unit/test_vm_client.py b/tests/unit/test_vm_client.py index 7cc9a2c3..d9a9a36b 100644 --- a/tests/unit/test_vm_client.py +++ b/tests/unit/test_vm_client.py @@ -290,8 +290,8 @@ async def test_vm_client_generate_correct_authentication_headers(): ) path, headers = await vm_client._generate_header(vm_id, "reboot", method="post") - signed_pubkey = SignedPubKeyHeader.parse_raw(headers["X-SignedPubKey"]) - signed_operation = SignedOperation.parse_raw(headers["X-SignedOperation"]) + signed_pubkey = SignedPubKeyHeader.model_validate_json(headers["X-SignedPubKey"]) + signed_operation = SignedOperation.model_validate_json(headers["X-SignedOperation"]) address = verify_signed_operation(signed_operation, signed_pubkey) assert vm_client.account.get_address() == address