Skip to content

Commit 9466fee

Browse files
nesitorClaudeamalcarazAndres D. Molins1yam
authored
Upgrade pydantic v1 to v2 (#771)
* Upgrade pydantic v1 to v2 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]> * Fix: Solve tests related with new `json` serialization format * Fix: Solve last test issue about dates * Fix: Use new version of `aleph-message` dependency instead the development one. * fix: wrong time property defined on MessageConfirmation schema * Fix: Solve code quality issue * Fix: Solve ValidationError about deprecated validation `each_item` used with new pydantic v2 annotations. * fix: remove debug print in format_message * fix: missing `start_date` `end_date` `start_block` `end_block` in BaseMessageQueryParams * fix: update field access method for Pydantic v2 in indexer_reader Change from using __annotations__.keys() to model_fields.keys() to properly extract field names from Pydantic v2 models when generating GraphQL queries. This fixes the ValidationError with missing required fields in IndexerEventResponse. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]> * fix: update field access method for Pydantic v2 in indexer_reader Change from using __annotations__.keys() to model_fields.keys() to properly extract field names from Pydantic v2 models when generating GraphQL queries. This fixes the ValidationError with missing required fields in IndexerEventResponse. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]> * Refactor: Remove params from MessageQueryParams class as the base one already implements and validates it. * Refactor: Simplify the way to get the model fields instead to create an empty instance. --------- Co-authored-by: Claude <[email protected]> Co-authored-by: amalcaraz <[email protected]> Co-authored-by: Andres D. Molins <[email protected]> Co-authored-by: 1yam <[email protected]>
1 parent 89ed12a commit 9466fee

36 files changed

+293
-262
lines changed

CHANGELOG.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22
Changelog
33
=========
44

5+
Version 0.5.0
6+
=============
7+
8+
* Feature: Migrated from Pydantic v1 to Pydantic v2
9+
* Removed dependency on dataclasses-json in favor of Pydantic models
10+
* Updated the JSON serialization to work with Pydantic v2
11+
* Updated schema validation to use the new Pydantic v2 API
12+
513
Version 0.4.7
614
=============
715

deployment/scripts/sync_initial_messages.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,14 @@
88
initial_messages_list = [
99
# Diagnostic VMs
1010
"cad11970efe9b7478300fd04d7cc91c646ca0a792b9cc718650f86e1ccfac73e", # Initial program
11-
"3fc0aa9569da840c43e7bd2033c3c580abb46b007527d6d20f2d4e98e867f7af", # DiagVM
11+
"3fc0aa9569da840c43e7bd2033c3c580abb46b007527d6d20f2d4e98e867f7af", # Old DiagVM Debian 12
12+
"63faf8b5db1cf8d965e6a464a0cb8062af8e7df131729e48738342d956f29ace", # Current Debian 12 DiagVM
1213
"67705389842a0a1b95eaa408b009741027964edc805997475e95c505d642edd8", # Legacy Diag VM
1314
# Volumes like runtimes, data, code, etc
14-
"6b8618f5b8913c0f582f1a771a154a556ee3fa3437ef3cf91097819910cf383b", # Diag VM code volume
15-
"f873715dc2feec3833074bd4b8745363a0e0093746b987b4c8191268883b2463", # Diag VM runtime volume
15+
"6b8618f5b8913c0f582f1a771a154a556ee3fa3437ef3cf91097819910cf383b", # Old Diag VM code volume
16+
"f873715dc2feec3833074bd4b8745363a0e0093746b987b4c8191268883b2463", # Old Diag VM runtime volume
17+
"79f19811f8e843f37ff7535f634b89504da3d8f03e1f0af109d1791cf6add7af", # Diag VM code volume
18+
"63f07193e6ee9d207b7d1fcf8286f9aee34e6f12f101d2ec77c1229f92964696", # Diag VM runtime volume
1619
"a92c81992e885d7a554fa78e255a5802404b7fdde5fbff20a443ccd13020d139", # Legacy Diag VM code volume
1720
"bd79839bf96e595a06da5ac0b6ba51dea6f7e2591bb913deccded04d831d29f4", # Legacy Diag VM runtime volume
1821
]

pyproject.toml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ dependencies = [
2828
"aiohttp-jinja2==1.6",
2929
"aioipfs~=0.7.1",
3030
"alembic==1.15.1",
31-
"aleph-message==0.6.1",
31+
"aleph-message==1.0.0",
3232
"aleph-nuls2==0.1",
3333
"aleph-p2p-client @ git+https://github.com/aleph-im/p2p-service-client-python@cbfebb871db94b2ca580e66104a67cd730c5020c",
3434
"asyncpg==0.30",
@@ -46,7 +46,8 @@ dependencies = [
4646
"multiaddr==0.0.9", # for libp2p-stubs
4747
"orjson>=3.7.7", # Minimum version for Python 3.11
4848
"psycopg2-binary==2.9.10", # Note: psycopg3 is not yet supported by SQLAlchemy
49-
"pycryptodome==3.22.0", # for libp2p-stubs
49+
"pycryptodome==3.22.0",
50+
"pydantic>=2.0.0,<3.0.0",
5051
"pymultihash==0.8.2", # for libp2p-stubs
5152
"pynacl==1.5",
5253
"pytezos-crypto==3.13.4.1",
@@ -62,6 +63,7 @@ dependencies = [
6263
"sqlalchemy-utils==0.41.2",
6364
"substrate-interface==1.7.11",
6465
"types-aiofiles==24.1.0.20241221",
66+
"typing-extensions>=4.6.1",
6567
"ujson==5.10.0", # required by aiocache
6668
"urllib3==2.3",
6769
"uvloop==0.21",
@@ -158,9 +160,7 @@ dependencies = [
158160
"isort==5.13.2",
159161
"check-sdist==0.1.3",
160162
"sqlalchemy[mypy]==1.4.41",
161-
"yamlfix==1.16.1",
162-
# because of aleph messages otherwise yamlfix install a too new version
163-
"pydantic>=1.10.5,<2.0.0",
163+
"yamlfix==1.17.0",
164164
"pyproject-fmt==2.2.1",
165165
"types-aiofiles",
166166
"types-protobuf",

src/aleph/chains/chain_data_service.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import asyncio
2+
import json
23
from typing import Any, Dict, List, Mapping, Optional, Self, Set, Type, Union, cast
34

45
import aio_pika.abc
@@ -66,10 +67,12 @@ async def prepare_sync_event_payload(
6667
protocol=ChainSyncProtocol.ON_CHAIN_SYNC,
6768
version=1,
6869
content=OnChainContent(
69-
messages=[OnChainMessage.from_orm(message) for message in messages]
70+
messages=[
71+
OnChainMessage.model_validate(message) for message in messages
72+
]
7073
),
7174
)
72-
archive_content: bytes = archive.json().encode("utf-8")
75+
archive_content: bytes = archive.model_dump_json().encode("utf-8")
7376

7477
ipfs_cid = await self.storage_service.add_file(
7578
session=session, file_content=archive_content, engine=ItemType.ipfs
@@ -166,7 +169,9 @@ def _get_tx_messages_smart_contract_protocol(tx: ChainTxDb) -> List[Dict[str, An
166169
)
167170

168171
try:
169-
payload = cast(GenericMessageEvent, payload_model.parse_obj(tx.content))
172+
payload = cast(
173+
GenericMessageEvent, payload_model.model_validate(tx.content)
174+
)
170175
except ValidationError:
171176
raise InvalidContent(f"Incompatible tx content for {tx.chain}/{tx.hash}")
172177

@@ -189,7 +194,7 @@ def _get_tx_messages_smart_contract_protocol(tx: ChainTxDb) -> List[Dict[str, An
189194
item_hash=ItemHash(payload.content),
190195
metadata=None,
191196
)
192-
item_content = content.json(exclude_none=True)
197+
item_content = json.dumps(content.model_dump(exclude_none=True))
193198
else:
194199
item_content = payload.content
195200

src/aleph/chains/indexer_reader.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -84,16 +84,16 @@ def make_events_query(
8484
if not datetime_range and not block_range:
8585
raise ValueError("A range of datetimes or blocks must be specified.")
8686

87-
model: Union[Type[MessageEvent], Type[SyncEvent]]
87+
model_fields: List[str]
8888

8989
if event_type == ChainEventType.MESSAGE:
90-
model = MessageEvent
9190
event_type_str = "messageEvents"
91+
model_fields = list(MessageEvent.model_fields.keys())
9292
else:
93-
model = SyncEvent
9493
event_type_str = "syncEvents"
94+
model_fields = list(SyncEvent.model_fields.keys())
9595

96-
fields = "\n".join(model.__fields__.keys())
96+
fields = "\n".join(model_fields)
9797
params: Dict[str, Any] = {
9898
"blockchain": f'"{blockchain.value}"',
9999
"limit": limit,
@@ -147,7 +147,7 @@ async def _query(self, query: str, model: Type[T]) -> T:
147147
response = await self.http_session.post("/", json={"query": query})
148148
response.raise_for_status()
149149
response_json = await response.json()
150-
return model.parse_obj(response_json)
150+
return model.model_validate(response_json)
151151

152152
async def fetch_account_state(
153153
self,
@@ -196,7 +196,7 @@ def indexer_event_to_chain_tx(
196196
if isinstance(indexer_event, MessageEvent):
197197
protocol = ChainSyncProtocol.SMART_CONTRACT
198198
protocol_version = 1
199-
content = indexer_event.dict()
199+
content = indexer_event.model_dump()
200200
else:
201201
sync_message = aleph_json.loads(indexer_event.message)
202202

src/aleph/chains/tezos.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ async def fetch_messages(
162162
response.raise_for_status()
163163
response_json = await response.json()
164164

165-
return IndexerResponse[IndexerMessageEvent].parse_obj(response_json)
165+
return IndexerResponse[IndexerMessageEvent].model_validate(response_json)
166166

167167

168168
def indexer_event_to_chain_tx(
@@ -176,7 +176,7 @@ def indexer_event_to_chain_tx(
176176
publisher=indexer_event.source,
177177
protocol=ChainSyncProtocol.SMART_CONTRACT,
178178
protocol_version=1,
179-
content=indexer_event.payload.dict(),
179+
content=indexer_event.payload.model_dump(),
180180
)
181181

182182
return chain_tx

src/aleph/db/models/messages.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
StoreContent,
1515
)
1616
from pydantic import ValidationError
17-
from pydantic.error_wrappers import ErrorWrapper
1817
from sqlalchemy import (
1918
ARRAY,
2019
TIMESTAMP,
@@ -62,14 +61,14 @@ def validate_message_content(
6261
content_dict: Dict[str, Any],
6362
) -> BaseContent:
6463
content_type = CONTENT_TYPE_MAP[message_type]
65-
content = content_type.parse_obj(content_dict)
64+
content = content_type.model_validate(content_dict)
6665
# Validate that the content time can be converted to datetime. This will
6766
# raise a ValueError and be caught
6867
# TODO: move this validation in aleph-message
6968
try:
7069
_ = dt.datetime.fromtimestamp(content_dict["time"])
7170
except ValueError as e:
72-
raise ValidationError([ErrorWrapper(e, loc="time")], model=content_type) from e
71+
raise ValidationError(str(e)) from e
7372

7473
return content
7574

src/aleph/handlers/content/vm.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ def vm_message_to_db(message: MessageDb) -> VmBaseDb:
195195

196196
if content.on.message:
197197
vm.message_triggers = [
198-
subscription.dict() for subscription in content.on.message
198+
subscription.model_dump() for subscription in content.on.message
199199
]
200200

201201
vm.code_volume = CodeVolumeDb(

src/aleph/schemas/api/accounts.py

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import datetime as dt
22
from decimal import Decimal
3-
from typing import Dict, List, Optional
3+
from typing import Annotated, Dict, List, Optional
44

55
from aleph_message.models import Chain
6-
from pydantic import BaseModel, Field, validator
6+
from pydantic import BaseModel, ConfigDict, Field, PlainSerializer, field_validator
77

88
from aleph.types.files import FileType
99
from aleph.types.sort_order import SortOrder
@@ -16,11 +16,16 @@ class GetAccountQueryParams(BaseModel):
1616
)
1717

1818

19+
FloatDecimal = Annotated[
20+
Decimal, PlainSerializer(lambda x: float(x), return_type=float, when_used="json")
21+
]
22+
23+
1924
class GetAccountBalanceResponse(BaseModel):
2025
address: str
21-
balance: Decimal
22-
details: Optional[Dict[str, Decimal]]
23-
locked_amount: Decimal
26+
balance: FloatDecimal
27+
details: Optional[Dict[str, FloatDecimal]] = None
28+
locked_amount: FloatDecimal
2429

2530

2631
class GetAccountFilesQueryParams(BaseModel):
@@ -53,16 +58,15 @@ class GetBalancesChainsQueryParams(BaseModel):
5358
)
5459
min_balance: int = Field(default=0, ge=1, description="Minimum Balance needed")
5560

56-
@validator("chains", pre=True)
61+
@field_validator("chains", mode="before")
5762
def split_str(cls, v):
5863
if isinstance(v, str):
5964
return v.split(LIST_FIELD_SEPARATOR)
6065
return v
6166

6267

6368
class AddressBalanceResponse(BaseModel):
64-
class Config:
65-
orm_mode = True
69+
model_config = ConfigDict(from_attributes=True)
6670

6771
address: str
6872
balance: str
@@ -78,8 +82,7 @@ class GetAccountFilesResponseItem(BaseModel):
7882

7983

8084
class GetAccountFilesResponse(BaseModel):
81-
class Config:
82-
orm_mode = True
85+
model_config = ConfigDict(from_attributes=True)
8386

8487
address: str
8588
total_size: int

src/aleph/schemas/api/costs.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,25 @@
11
from typing import List
22

3-
from pydantic import BaseModel, validator
3+
from pydantic import BaseModel, ConfigDict, field_validator
44

55
from aleph.toolkit.costs import format_cost_str
66

77

88
class EstimatedCostDetailResponse(BaseModel):
9-
class Config:
10-
orm_mode = True
9+
model_config = ConfigDict(from_attributes=True)
1110

1211
type: str
1312
name: str
1413
cost_hold: str
1514
cost_stream: str
1615

17-
@validator("cost_hold", "cost_stream")
16+
@field_validator("cost_hold", "cost_stream")
1817
def check_format_price(cls, v):
1918
return format_cost_str(v)
2019

2120

2221
class EstimatedCostsResponse(BaseModel):
23-
class Config:
24-
orm_mode = True
22+
model_config = ConfigDict(from_attributes=True)
2523

2624
required_tokens: float
2725
payment_type: str

0 commit comments

Comments
 (0)