Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

177 upgrade pydantic version #179

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open
7 changes: 4 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,16 @@ dynamic = [ "version" ]
dependencies = [
"aiohttp>=3.8.3",
"aioresponses>=0.7.6",
"aleph-message>=0.4.9",
"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-settings>=2",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this dependency ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

By moving to pydantic v2, the settings management has been moved to a separate package called pydantic-settings so if we don't import it, it causes some errors

"pynacl==1.5", # Needed now as default with _load_account changement
"python-magic",
"typing-extensions",
"web3==6.3",
Expand Down
2 changes: 1 addition & 1 deletion src/aleph/sdk/chains/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this added here ?

Copy link
Member Author

@Antonyjin Antonyjin Oct 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, my mistake. Normally, .hex() should not remove the 0x at the beginning of the signature, but after the changes made for the Pydantic upgrade, it remove it.
I was just missing this, so I added it temporarily to check if everything would pass and completely forgot to correct it.

I'll debug to see why the hex remove the 0x

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found out that with python3>=3.5 .hex() should remove the 0x prefix from the string.
My branch does it so it causes some errors in the tests but on the main branch it seems that the prefix is not removed even though the python version is 3.11.5

return message

@abstractmethod
Expand Down
4 changes: 2 additions & 2 deletions src/aleph/sdk/chains/remote.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this related to pydantic ? Should this use await or should the implementation not be async ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not related to pydantic, just saw some warning when lauching the tests, so I tried to fix some of them because it wasn't a lot of work.

data = await response.json()
properties = AccountProperties(**data)

Expand All @@ -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()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same: Is this related to pydantic ? Should this use await or should the implementation not be async ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same: This is not related to pydantic, just saw some warning when lauching the tests, so I tried to fix some of them because it wasn't a lot of work.

return await response.json()

async def sign_raw(self, buffer: bytes) -> bytes:
Expand Down
20 changes: 10 additions & 10 deletions src/aleph/sdk/client/authenticated_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,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={
Expand Down Expand Up @@ -301,7 +301,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,
Expand Down Expand Up @@ -329,7 +329,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,
Expand Down Expand Up @@ -403,7 +403,7 @@ async def create_store(
content = StoreContent(**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,
Expand Down Expand Up @@ -496,7 +496,7 @@ async def create_program(
assert content.on.persistent == persistent

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,
Expand Down Expand Up @@ -572,7 +572,7 @@ async def create_instance(
payment=payment,
)
message, status, response = await self.submit(
content=content.dict(exclude_none=True),
content=content.model_dump(exclude_none=True),
message_type=MessageType.instance,
channel=channel,
storage_engine=storage_engine,
Expand Down Expand Up @@ -618,7 +618,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,
Expand Down Expand Up @@ -662,11 +662,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(
Expand Down Expand Up @@ -710,7 +710,7 @@ async def _upload_file_native(
item_hash=file_hash,
mime_type=mime_type,
time=time.time(),
**extra_fields,
**(extra_fields or {}),
)
message, _ = await self._storage_push_file_with_message(
file_content=file_content,
Expand Down
5 changes: 4 additions & 1 deletion src/aleph/sdk/client/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,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
Expand Down Expand Up @@ -467,3 +467,6 @@ async def get_message_status(self, item_hash: str) -> MessageStatus:
if resp.status == HTTPNotFound.status_code:
raise MessageNotFoundError(f"No such hash {item_hash}")
resp.raise_for_status()

data = await resp.json()
return MessageStatus(**data)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was there no return before ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right. When I initially tested upgrading to Pydantic v2, I encountered an error saying that a return value was required, so I added it. I just checked again, and the error is no longer there, so I may have done it incorrectly. I'll remove it in the next push.

2 changes: 1 addition & 1 deletion src/aleph/sdk/client/vm_confidential_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
59 changes: 29 additions & 30 deletions src/aleph/sdk/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -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, ConfigDict, Field
from pydantic_settings import BaseSettings

from aleph.sdk.types import ChainInfo

Expand Down Expand Up @@ -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: float = 15.0

DEFAULT_CHANNEL: str = "ALEPH-CLOUDSOLUTIONS"
DEFAULT_RUNTIME_ID: str = (
Expand Down Expand Up @@ -83,12 +84,12 @@ 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"
VM_URL_PATH: ClassVar[str] = "https://aleph.sh/vm/{hash}"
VM_URL_HOST: ClassVar[str] = "https://{hash_base32}.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(
Expand Down Expand Up @@ -124,28 +125,27 @@ class Settings(BaseSettings):
),
}
# Add all placeholders to allow easy dynamic setup of CHAINS
CHAINS_SEPOLIA_ACTIVE: Optional[bool]
CHAINS_ETH_ACTIVE: Optional[bool]
CHAINS_AVAX_ACTIVE: Optional[bool]
CHAINS_BASE_ACTIVE: Optional[bool]
CHAINS_BSC_ACTIVE: Optional[bool]
CHAINS_SEPOLIA_RPC: Optional[str]
CHAINS_ETH_RPC: Optional[str]
CHAINS_AVAX_RPC: Optional[str]
CHAINS_BASE_RPC: Optional[str]
CHAINS_BSC_RPC: Optional[str]
CHAINS_SEPOLIA_ACTIVE: Optional[bool] = None
CHAINS_ETH_ACTIVE: Optional[bool] = None
CHAINS_AVAX_ACTIVE: Optional[bool] = None
CHAINS_BASE_ACTIVE: Optional[bool] = None
CHAINS_BSC_ACTIVE: Optional[bool] = None
CHAINS_SEPOLIA_RPC: Optional[str] = None
CHAINS_ETH_RPC: Optional[str] = None
CHAINS_AVAX_RPC: Optional[str] = None
CHAINS_BASE_RPC: Optional[str] = None
CHAINS_BSC_RPC: Optional[str] = None

# 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 = ConfigDict(
env_prefix="ALEPH_", case_sensitive=False, env_file=".env"
)


class MainConfiguration(BaseModel):
Expand All @@ -156,8 +156,7 @@ class MainConfiguration(BaseModel):
path: Path
chain: Chain

class Config:
use_enum_values = True
model_config = ConfigDict(use_enum_values=True)


# Settings singleton
Expand Down Expand Up @@ -213,7 +212,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)

Expand Down
10 changes: 5 additions & 5 deletions src/aleph/sdk/query/responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
ItemType,
MessageConfirmation,
)
from pydantic import BaseModel, Field
from pydantic import BaseModel, ConfigDict, Field


class Post(BaseModel):
Expand Down Expand Up @@ -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):
Expand All @@ -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):
Expand Down
10 changes: 3 additions & 7 deletions src/aleph/sdk/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@
from uuid import UUID
from zipfile import BadZipFile, ZipFile

import pydantic_core
from aleph_message.models import ItemHash, MessageType
from aleph_message.models.execution.program import Encoding
from aleph_message.models.execution.volume import MachineVolume
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
Expand Down Expand Up @@ -173,19 +173,15 @@ 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:
# Python 3.9 does not support `isinstance(volume_dict, MachineVolume)`,
# so we need to iterate over all types.
if any(
isinstance(volume_dict, volume_type) for volume_type in get_args(MachineVolume)
):
return volume_dict
for volume_type in get_args(MachineVolume):
try:
return volume_type.parse_obj(volume_dict)
return volume_type.model_validate(volume_dict)
except ValueError:
continue
else:
Expand Down
Loading
Loading