Skip to content

Commit 292fba7

Browse files
committed
Replace deprecated functions with new pydantic v2 functions
1 parent 38ad2c4 commit 292fba7

File tree

15 files changed

+53
-42
lines changed

15 files changed

+53
-42
lines changed

bot/constants.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import os
99
from enum import Enum
1010

11-
from pydantic import BaseModel, root_validator
11+
from pydantic import BaseModel, model_validator
1212
from pydantic_settings import BaseSettings
1313

1414

@@ -311,7 +311,7 @@ class _Colours(EnvConfig, env_prefix="colours_"):
311311
white: int = 0xfffffe
312312
yellow: int = 0xffd241
313313

314-
@root_validator(pre=True)
314+
@model_validator(mode="before")
315315
def parse_hex_values(cls, values: dict) -> dict: # noqa: N805
316316
"""Convert hex strings to ints."""
317317
for key, value in values.items():

bot/exts/filtering/_filter_lists/antispam.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ async def actions_for(
9393
current_actions.pop("ping", None)
9494
current_actions.pop("send_alert", None)
9595

96-
new_infraction = current_actions[InfractionAndNotification.name].copy()
96+
new_infraction = current_actions[InfractionAndNotification.name].model_copy()
9797
# Smaller infraction value => higher in hierarchy.
9898
if not current_infraction or new_infraction.infraction_type.value < current_infraction.value:
9999
# Pick the first triggered filter for the reason, there's no good way to decide between them.

bot/exts/filtering/_filters/filter.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ def __init__(self, filter_data: dict, defaults: Defaults | None = None):
3131
self.updated_at = arrow.get(filter_data["updated_at"])
3232
self.actions, self.validations = create_settings(filter_data["settings"], defaults=defaults)
3333
if self.extra_fields_type:
34-
self.extra_fields = self.extra_fields_type.parse_obj(filter_data["additional_settings"])
34+
self.extra_fields = self.extra_fields_type.model_validate(filter_data["additional_settings"])
3535
else:
3636
self.extra_fields = None
3737

@@ -46,7 +46,7 @@ def overrides(self) -> tuple[dict[str, Any], dict[str, Any]]:
4646

4747
filter_settings = {}
4848
if self.extra_fields:
49-
filter_settings = self.extra_fields.dict(exclude_unset=True)
49+
filter_settings = self.extra_fields.model_dump(exclude_unset=True)
5050

5151
return settings, filter_settings
5252

bot/exts/filtering/_settings.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -227,5 +227,5 @@ def dict(self) -> dict[str, Any]:
227227
"""Return a dict representation of the stored fields across all entries."""
228228
dict_ = {}
229229
for settings in self:
230-
dict_ = reduce(operator.or_, (entry.dict() for entry in settings.values()), dict_)
230+
dict_ = reduce(operator.or_, (entry.model_dump() for entry in settings.values()), dict_)
231231
return dict_

bot/exts/filtering/_settings_types/actions/infraction_and_notification.py

+8-8
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from dateutil.relativedelta import relativedelta
77
from discord import Colour, Embed, Member, User
88
from discord.errors import Forbidden
9-
from pydantic import validator
9+
from pydantic import field_validator
1010
from pydis_core.utils.logging import get_logger
1111
from pydis_core.utils.members import get_or_fetch_member
1212

@@ -151,7 +151,7 @@ class InfractionAndNotification(ActionEntry):
151151
infraction_duration: InfractionDuration
152152
infraction_channel: int
153153

154-
@validator("infraction_type", pre=True)
154+
@field_validator("infraction_type", mode="before")
155155
@classmethod
156156
def convert_infraction_name(cls, infr_type: str | Infraction) -> Infraction:
157157
"""Convert the string to an Infraction by name."""
@@ -221,24 +221,24 @@ def union(self, other: Self) -> Self:
221221
"""
222222
# Lower number -> higher in the hierarchy
223223
if self.infraction_type is None:
224-
return other.copy()
224+
return other.model_copy()
225225
if other.infraction_type is None:
226-
return self.copy()
226+
return self.model_copy()
227227

228228
if self.infraction_type.value < other.infraction_type.value:
229-
result = self.copy()
229+
result = self.model_copy()
230230
elif self.infraction_type.value > other.infraction_type.value:
231-
result = other.copy()
231+
result = other.model_copy()
232232
other = self
233233
else:
234234
now = arrow.utcnow().datetime
235235
if self.infraction_duration is None or (
236236
other.infraction_duration is not None
237237
and now + self.infraction_duration.value > now + other.infraction_duration.value
238238
):
239-
result = self.copy()
239+
result = self.model_copy()
240240
else:
241-
result = other.copy()
241+
result = other.model_copy()
242242
other = self
243243

244244
# If the winner has no message but the loser does, copy the message to the winner.

bot/exts/filtering/_settings_types/actions/ping.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import ClassVar, Self
22

3-
from pydantic import validator
3+
from pydantic import field_validator
44

55
from bot.exts.filtering._filter_context import FilterContext
66
from bot.exts.filtering._settings_types.settings_entry import ActionEntry
@@ -25,7 +25,7 @@ class Ping(ActionEntry):
2525
guild_pings: set[str]
2626
dm_pings: set[str]
2727

28-
@validator("*", pre=True)
28+
@field_validator("*", mode="before")
2929
@classmethod
3030
def init_sequence_if_none(cls, pings: list[str] | None) -> list[str]:
3131
"""Initialize an empty sequence if the value is None."""

bot/exts/filtering/_settings_types/settings_entry.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ class SettingsEntry(BaseModel, FieldRequiring):
2828
def __init__(self, defaults: SettingsEntry | None = None, /, **data):
2929
overrides = set()
3030
if defaults:
31-
defaults_dict = defaults.dict()
31+
defaults_dict = defaults.model_dump()
3232
for field_name, field_value in list(data.items()):
3333
if field_value is None:
3434
data[field_name] = defaults_dict[field_name]

bot/exts/filtering/_settings_types/validations/channel_scope.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import ClassVar, Union
22

3-
from pydantic import validator
3+
from pydantic import field_validator
44

55
from bot.exts.filtering._filter_context import FilterContext
66
from bot.exts.filtering._settings_types.settings_entry import ValidationEntry
@@ -36,7 +36,7 @@ class ChannelScope(ValidationEntry):
3636
enabled_channels: set[Union[int, str]] # noqa: UP007
3737
enabled_categories: set[Union[int, str]] # noqa: UP007
3838

39-
@validator("*", pre=True)
39+
@field_validator("*", mode="before")
4040
@classmethod
4141
def init_if_sequence_none(cls, sequence: list[str] | None) -> list[str]:
4242
"""Initialize an empty sequence if the value is None."""

bot/exts/filtering/_ui/filter.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ def build_filter_repr_dict(
3434
default_setting_values = {}
3535
for settings_group in filter_list[list_type].defaults:
3636
for _, setting in settings_group.items():
37-
default_setting_values.update(to_serializable(setting.dict(), ui_repr=True))
37+
default_setting_values.update(to_serializable(setting.model_dump(), ui_repr=True))
3838

3939
# Add overrides. It's done in this way to preserve field order, since the filter won't have all settings.
4040
total_values = {}
@@ -47,7 +47,7 @@ def build_filter_repr_dict(
4747
# Add the filter-specific settings.
4848
if filter_type.extra_fields_type:
4949
# This iterates over the default values of the extra fields model.
50-
for name, value in filter_type.extra_fields_type().dict().items():
50+
for name, value in filter_type.extra_fields_type().model_dump().items():
5151
if name not in extra_fields_overrides or repr_equals(extra_fields_overrides[name], value):
5252
total_values[f"{filter_type.name}/{name}"] = value
5353
else:
@@ -287,7 +287,7 @@ async def update_embed(
287287
if "/" in setting_name:
288288
filter_name, setting_name = setting_name.split("/", maxsplit=1)
289289
dict_to_edit = self.filter_settings_overrides
290-
default_value = self.filter_type.extra_fields_type().dict()[setting_name]
290+
default_value = self.filter_type.extra_fields_type().model_dump()[setting_name]
291291
else:
292292
dict_to_edit = self.settings_overrides
293293
default_value = self.filter_list[self.list_type].default(setting_name)

bot/exts/filtering/_ui/filter_list.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ def build_filterlist_repr_dict(filter_list: FilterList, list_type: ListType, new
5050
default_setting_values = {}
5151
for settings_group in filter_list[list_type].defaults:
5252
for _, setting in settings_group.items():
53-
default_setting_values.update(to_serializable(setting.dict(), ui_repr=True))
53+
default_setting_values.update(to_serializable(setting.model_dump(), ui_repr=True))
5454

5555
# Add new values. It's done in this way to preserve field order, since the new_values won't have all settings.
5656
total_values = {}

bot/exts/filtering/_utils.py

+9-4
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,15 @@
77
import types
88
from abc import ABC, abstractmethod
99
from collections import defaultdict
10-
from collections.abc import Iterable
10+
from collections.abc import Callable, Iterable
1111
from dataclasses import dataclass
1212
from functools import cache
1313
from typing import Any, Self, TypeVar, Union, get_args, get_origin
1414

1515
import discord
1616
import regex
1717
from discord.ext.commands import Command
18+
from pydantic_core import core_schema
1819

1920
import bot
2021
from bot.bot import Bot
@@ -252,12 +253,16 @@ def __init__(self, value: Any):
252253
self.value = self.process_value(value)
253254

254255
@classmethod
255-
def __get_validators__(cls):
256+
def __get_pydantic_core_schema__(
257+
cls,
258+
_source: type[Any],
259+
_handler: Callable[[Any], core_schema.CoreSchema],
260+
) -> core_schema.CoreSchema:
256261
"""Boilerplate for Pydantic."""
257-
yield cls.validate
262+
return core_schema.general_plain_validator_function(cls.validate)
258263

259264
@classmethod
260-
def validate(cls, v: Any) -> Self:
265+
def validate(cls, v: Any, _info: core_schema.ValidationInfo) -> Self:
261266
"""Takes the given value and returns a class instance with that value."""
262267
if isinstance(v, CustomIOField):
263268
return cls(v.value)

bot/exts/filtering/filtering.py

+10-4
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ def collect_loaded_types(self, example_list: AtomicList) -> None:
181181
extra_fields_type,
182182
type_hints[field_name]
183183
)
184-
for field_name in extra_fields_type.__fields__
184+
for field_name in extra_fields_type.model_fields
185185
}
186186

187187
async def schedule_offending_messages_deletion(self) -> None:
@@ -754,7 +754,7 @@ async def fl_describe(
754754
setting_values = {}
755755
for settings_group in filter_list[list_type].defaults:
756756
for _, setting in settings_group.items():
757-
setting_values.update(to_serializable(setting.dict(), ui_repr=True))
757+
setting_values.update(to_serializable(setting.model_dump(), ui_repr=True))
758758

759759
embed = Embed(colour=Colour.blue())
760760
populate_embed_from_dict(embed, setting_values)
@@ -1239,7 +1239,13 @@ async def _patch_filter(
12391239
for current_settings in (filter_.actions, filter_.validations):
12401240
if current_settings:
12411241
for setting_entry in current_settings.values():
1242-
settings.update({setting: None for setting in setting_entry.dict() if setting not in settings})
1242+
settings.update(
1243+
{
1244+
setting: None
1245+
for setting in setting_entry.model_dump()
1246+
if setting not in settings
1247+
}
1248+
)
12431249

12441250
# Even though the list ID remains unchanged, it still needs to be provided for correct serializer validation.
12451251
list_id = filter_list[list_type].id
@@ -1295,7 +1301,7 @@ def _filter_match_query(
12951301
if not (differ_by_default <= override_matches): # The overrides didn't cover for the default mismatches.
12961302
return False
12971303

1298-
filter_settings = filter_.extra_fields.dict() if filter_.extra_fields else {}
1304+
filter_settings = filter_.extra_fields.model_dump() if filter_.extra_fields else {}
12991305
# If the dict changes then some fields were not the same.
13001306
return (filter_settings | filter_settings_query) == filter_settings
13011307

bot/exts/recruitment/talentpool/_api.py

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from datetime import datetime
22

3-
from pydantic import BaseModel, Field, parse_obj_as
3+
from pydantic import BaseModel, Field, TypeAdapter
44
from pydis_core.site_api import APIClient
55

66

@@ -50,13 +50,13 @@ async def get_nominations(
5050
params["user__id"] = str(user_id)
5151

5252
data = await self.site_api.get("bot/nominations", params=params)
53-
nominations = parse_obj_as(list[Nomination], data)
53+
nominations = TypeAdapter(list[Nomination]).validate_python(data)
5454
return nominations
5555

5656
async def get_nomination(self, nomination_id: int) -> Nomination:
5757
"""Fetch a nomination by ID."""
5858
data = await self.site_api.get(f"bot/nominations/{nomination_id}")
59-
nomination = Nomination.parse_obj(data)
59+
nomination = Nomination.model_validate(data)
6060
return nomination
6161

6262
async def edit_nomination(
@@ -84,7 +84,7 @@ async def edit_nomination(
8484
data["thread_id"] = thread_id
8585

8686
result = await self.site_api.patch(f"bot/nominations/{nomination_id}", json=data)
87-
return Nomination.parse_obj(result)
87+
return Nomination.model_validate(result)
8888

8989
async def edit_nomination_entry(
9090
self,
@@ -96,7 +96,7 @@ async def edit_nomination_entry(
9696
"""Edit a nomination entry."""
9797
data = {"actor": actor_id, "reason": reason}
9898
result = await self.site_api.patch(f"bot/nominations/{nomination_id}", json=data)
99-
return Nomination.parse_obj(result)
99+
return Nomination.model_validate(result)
100100

101101
async def post_nomination(
102102
self,
@@ -111,7 +111,7 @@ async def post_nomination(
111111
"user": user_id,
112112
}
113113
result = await self.site_api.post("bot/nominations", json=data)
114-
return Nomination.parse_obj(result)
114+
return Nomination.model_validate(result)
115115

116116
async def get_activity(
117117
self,

botstrap.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ def create_webhook(self, name: str, channel_id_: int) -> str:
176176

177177
all_roles = discord_client.get_all_roles()
178178

179-
for role_name in _Roles.__fields__:
179+
for role_name in _Roles.model_fields:
180180

181181
role_id = all_roles.get(role_name, None)
182182
if not role_id:
@@ -209,7 +209,7 @@ def create_webhook(self, name: str, channel_id_: int) -> str:
209209
python_help_channel_id = discord_client.create_forum_channel(python_help_channel_name, python_help_category_id)
210210
all_channels[PYTHON_HELP_CHANNEL_NAME] = python_help_channel_id
211211

212-
for channel_name in _Channels.__fields__:
212+
for channel_name in _Channels.model_fields:
213213
channel_id = all_channels.get(channel_name, None)
214214
if not channel_id:
215215
log.warning(
@@ -222,7 +222,7 @@ def create_webhook(self, name: str, channel_id_: int) -> str:
222222

223223
config_str += "\n#Categories\n"
224224

225-
for category_name in _Categories.__fields__:
225+
for category_name in _Categories.model_fields:
226226
category_id = all_categories.get(category_name, None)
227227
if not category_id:
228228
log.warning(

tests/bot/exts/filtering/test_settings_entries.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ def test_infraction_merge_of_same_infraction_type(self):
173173
result = infraction1.union(infraction2)
174174

175175
self.assertDictEqual(
176-
result.dict(),
176+
result.model_dump(),
177177
{
178178
"infraction_type": Infraction.TIMEOUT,
179179
"infraction_reason": "there",
@@ -206,7 +206,7 @@ def test_infraction_merge_of_different_infraction_types(self):
206206
result = infraction1.union(infraction2)
207207

208208
self.assertDictEqual(
209-
result.dict(),
209+
result.model_dump(),
210210
{
211211
"infraction_type": Infraction.BAN,
212212
"infraction_reason": "",

0 commit comments

Comments
 (0)