aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--bot/constants.py4
-rw-r--r--bot/exts/filtering/_filter_lists/antispam.py2
-rw-r--r--bot/exts/filtering/_filters/filter.py4
-rw-r--r--bot/exts/filtering/_settings.py2
-rw-r--r--bot/exts/filtering/_settings_types/actions/infraction_and_notification.py16
-rw-r--r--bot/exts/filtering/_settings_types/actions/ping.py4
-rw-r--r--bot/exts/filtering/_settings_types/settings_entry.py2
-rw-r--r--bot/exts/filtering/_settings_types/validations/channel_scope.py4
-rw-r--r--bot/exts/filtering/_ui/filter.py6
-rw-r--r--bot/exts/filtering/_ui/filter_list.py2
-rw-r--r--bot/exts/filtering/_utils.py13
-rw-r--r--bot/exts/filtering/filtering.py14
-rw-r--r--bot/exts/recruitment/talentpool/_api.py12
-rw-r--r--botstrap.py6
-rw-r--r--tests/bot/exts/filtering/test_settings_entries.py4
15 files changed, 53 insertions, 42 deletions
diff --git a/bot/constants.py b/bot/constants.py
index 922f41abd..fce3c09ec 100644
--- a/bot/constants.py
+++ b/bot/constants.py
@@ -8,7 +8,7 @@ By default, the values defined in the classes are used, these can be overridden
import os
from enum import Enum
-from pydantic import BaseModel, root_validator
+from pydantic import BaseModel, model_validator
from pydantic_settings import BaseSettings
@@ -311,7 +311,7 @@ class _Colours(EnvConfig, env_prefix="colours_"):
white: int = 0xfffffe
yellow: int = 0xffd241
- @root_validator(pre=True)
+ @model_validator(mode="before")
def parse_hex_values(cls, values: dict) -> dict: # noqa: N805
"""Convert hex strings to ints."""
for key, value in values.items():
diff --git a/bot/exts/filtering/_filter_lists/antispam.py b/bot/exts/filtering/_filter_lists/antispam.py
index 22f35f40e..f27412e1a 100644
--- a/bot/exts/filtering/_filter_lists/antispam.py
+++ b/bot/exts/filtering/_filter_lists/antispam.py
@@ -93,7 +93,7 @@ class AntispamList(UniquesListBase):
current_actions.pop("ping", None)
current_actions.pop("send_alert", None)
- new_infraction = current_actions[InfractionAndNotification.name].copy()
+ new_infraction = current_actions[InfractionAndNotification.name].model_copy()
# Smaller infraction value => higher in hierarchy.
if not current_infraction or new_infraction.infraction_type.value < current_infraction.value:
# Pick the first triggered filter for the reason, there's no good way to decide between them.
diff --git a/bot/exts/filtering/_filters/filter.py b/bot/exts/filtering/_filters/filter.py
index 6745e4f66..3f201cfde 100644
--- a/bot/exts/filtering/_filters/filter.py
+++ b/bot/exts/filtering/_filters/filter.py
@@ -31,7 +31,7 @@ class Filter(FieldRequiring):
self.updated_at = arrow.get(filter_data["updated_at"])
self.actions, self.validations = create_settings(filter_data["settings"], defaults=defaults)
if self.extra_fields_type:
- self.extra_fields = self.extra_fields_type.parse_obj(filter_data["additional_settings"])
+ self.extra_fields = self.extra_fields_type.model_validate(filter_data["additional_settings"])
else:
self.extra_fields = None
@@ -46,7 +46,7 @@ class Filter(FieldRequiring):
filter_settings = {}
if self.extra_fields:
- filter_settings = self.extra_fields.dict(exclude_unset=True)
+ filter_settings = self.extra_fields.model_dump(exclude_unset=True)
return settings, filter_settings
diff --git a/bot/exts/filtering/_settings.py b/bot/exts/filtering/_settings.py
index 766a5ea10..7005dd2d1 100644
--- a/bot/exts/filtering/_settings.py
+++ b/bot/exts/filtering/_settings.py
@@ -227,5 +227,5 @@ class Defaults(NamedTuple):
"""Return a dict representation of the stored fields across all entries."""
dict_ = {}
for settings in self:
- dict_ = reduce(operator.or_, (entry.dict() for entry in settings.values()), dict_)
+ dict_ = reduce(operator.or_, (entry.model_dump() for entry in settings.values()), dict_)
return dict_
diff --git a/bot/exts/filtering/_settings_types/actions/infraction_and_notification.py b/bot/exts/filtering/_settings_types/actions/infraction_and_notification.py
index f12538294..359aa7bc3 100644
--- a/bot/exts/filtering/_settings_types/actions/infraction_and_notification.py
+++ b/bot/exts/filtering/_settings_types/actions/infraction_and_notification.py
@@ -6,7 +6,7 @@ import discord.abc
from dateutil.relativedelta import relativedelta
from discord import Colour, Embed, Member, User
from discord.errors import Forbidden
-from pydantic import validator
+from pydantic import field_validator
from pydis_core.utils.logging import get_logger
from pydis_core.utils.members import get_or_fetch_member
@@ -151,7 +151,7 @@ class InfractionAndNotification(ActionEntry):
infraction_duration: InfractionDuration
infraction_channel: int
- @validator("infraction_type", pre=True)
+ @field_validator("infraction_type", mode="before")
@classmethod
def convert_infraction_name(cls, infr_type: str | Infraction) -> Infraction:
"""Convert the string to an Infraction by name."""
@@ -221,14 +221,14 @@ class InfractionAndNotification(ActionEntry):
"""
# Lower number -> higher in the hierarchy
if self.infraction_type is None:
- return other.copy()
+ return other.model_copy()
if other.infraction_type is None:
- return self.copy()
+ return self.model_copy()
if self.infraction_type.value < other.infraction_type.value:
- result = self.copy()
+ result = self.model_copy()
elif self.infraction_type.value > other.infraction_type.value:
- result = other.copy()
+ result = other.model_copy()
other = self
else:
now = arrow.utcnow().datetime
@@ -236,9 +236,9 @@ class InfractionAndNotification(ActionEntry):
other.infraction_duration is not None
and now + self.infraction_duration.value > now + other.infraction_duration.value
):
- result = self.copy()
+ result = self.model_copy()
else:
- result = other.copy()
+ result = other.model_copy()
other = self
# If the winner has no message but the loser does, copy the message to the winner.
diff --git a/bot/exts/filtering/_settings_types/actions/ping.py b/bot/exts/filtering/_settings_types/actions/ping.py
index fa3e2224f..4b38a19f3 100644
--- a/bot/exts/filtering/_settings_types/actions/ping.py
+++ b/bot/exts/filtering/_settings_types/actions/ping.py
@@ -1,6 +1,6 @@
from typing import ClassVar, Self
-from pydantic import validator
+from pydantic import field_validator
from bot.exts.filtering._filter_context import FilterContext
from bot.exts.filtering._settings_types.settings_entry import ActionEntry
@@ -25,7 +25,7 @@ class Ping(ActionEntry):
guild_pings: set[str]
dm_pings: set[str]
- @validator("*", pre=True)
+ @field_validator("*", mode="before")
@classmethod
def init_sequence_if_none(cls, pings: list[str] | None) -> list[str]:
"""Initialize an empty sequence if the value is None."""
diff --git a/bot/exts/filtering/_settings_types/settings_entry.py b/bot/exts/filtering/_settings_types/settings_entry.py
index a3a2cbe1a..1cb9d9f2a 100644
--- a/bot/exts/filtering/_settings_types/settings_entry.py
+++ b/bot/exts/filtering/_settings_types/settings_entry.py
@@ -28,7 +28,7 @@ class SettingsEntry(BaseModel, FieldRequiring):
def __init__(self, defaults: SettingsEntry | None = None, /, **data):
overrides = set()
if defaults:
- defaults_dict = defaults.dict()
+ defaults_dict = defaults.model_dump()
for field_name, field_value in list(data.items()):
if field_value is None:
data[field_name] = defaults_dict[field_name]
diff --git a/bot/exts/filtering/_settings_types/validations/channel_scope.py b/bot/exts/filtering/_settings_types/validations/channel_scope.py
index 1880ae27e..69de4199c 100644
--- a/bot/exts/filtering/_settings_types/validations/channel_scope.py
+++ b/bot/exts/filtering/_settings_types/validations/channel_scope.py
@@ -1,6 +1,6 @@
from typing import ClassVar, Union
-from pydantic import validator
+from pydantic import field_validator
from bot.exts.filtering._filter_context import FilterContext
from bot.exts.filtering._settings_types.settings_entry import ValidationEntry
@@ -36,7 +36,7 @@ class ChannelScope(ValidationEntry):
enabled_channels: set[Union[int, str]] # noqa: UP007
enabled_categories: set[Union[int, str]] # noqa: UP007
- @validator("*", pre=True)
+ @field_validator("*", mode="before")
@classmethod
def init_if_sequence_none(cls, sequence: list[str] | None) -> list[str]:
"""Initialize an empty sequence if the value is None."""
diff --git a/bot/exts/filtering/_ui/filter.py b/bot/exts/filtering/_ui/filter.py
index 4300de19c..6927a5d41 100644
--- a/bot/exts/filtering/_ui/filter.py
+++ b/bot/exts/filtering/_ui/filter.py
@@ -34,7 +34,7 @@ def build_filter_repr_dict(
default_setting_values = {}
for settings_group in filter_list[list_type].defaults:
for _, setting in settings_group.items():
- default_setting_values.update(to_serializable(setting.dict(), ui_repr=True))
+ default_setting_values.update(to_serializable(setting.model_dump(), ui_repr=True))
# Add overrides. It's done in this way to preserve field order, since the filter won't have all settings.
total_values = {}
@@ -47,7 +47,7 @@ def build_filter_repr_dict(
# Add the filter-specific settings.
if filter_type.extra_fields_type:
# This iterates over the default values of the extra fields model.
- for name, value in filter_type.extra_fields_type().dict().items():
+ for name, value in filter_type.extra_fields_type().model_dump().items():
if name not in extra_fields_overrides or repr_equals(extra_fields_overrides[name], value):
total_values[f"{filter_type.name}/{name}"] = value
else:
@@ -287,7 +287,7 @@ class FilterEditView(EditBaseView):
if "/" in setting_name:
filter_name, setting_name = setting_name.split("/", maxsplit=1)
dict_to_edit = self.filter_settings_overrides
- default_value = self.filter_type.extra_fields_type().dict()[setting_name]
+ default_value = self.filter_type.extra_fields_type().model_dump()[setting_name]
else:
dict_to_edit = self.settings_overrides
default_value = self.filter_list[self.list_type].default(setting_name)
diff --git a/bot/exts/filtering/_ui/filter_list.py b/bot/exts/filtering/_ui/filter_list.py
index 062975ad7..2858817e3 100644
--- a/bot/exts/filtering/_ui/filter_list.py
+++ b/bot/exts/filtering/_ui/filter_list.py
@@ -50,7 +50,7 @@ def build_filterlist_repr_dict(filter_list: FilterList, list_type: ListType, new
default_setting_values = {}
for settings_group in filter_list[list_type].defaults:
for _, setting in settings_group.items():
- default_setting_values.update(to_serializable(setting.dict(), ui_repr=True))
+ default_setting_values.update(to_serializable(setting.model_dump(), ui_repr=True))
# Add new values. It's done in this way to preserve field order, since the new_values won't have all settings.
total_values = {}
diff --git a/bot/exts/filtering/_utils.py b/bot/exts/filtering/_utils.py
index e109a47ee..944cf3837 100644
--- a/bot/exts/filtering/_utils.py
+++ b/bot/exts/filtering/_utils.py
@@ -7,7 +7,7 @@ import pkgutil
import types
from abc import ABC, abstractmethod
from collections import defaultdict
-from collections.abc import Iterable
+from collections.abc import Callable, Iterable
from dataclasses import dataclass
from functools import cache
from typing import Any, Self, TypeVar, Union, get_args, get_origin
@@ -15,6 +15,7 @@ from typing import Any, Self, TypeVar, Union, get_args, get_origin
import discord
import regex
from discord.ext.commands import Command
+from pydantic_core import core_schema
import bot
from bot.bot import Bot
@@ -252,12 +253,16 @@ class CustomIOField:
self.value = self.process_value(value)
@classmethod
- def __get_validators__(cls):
+ def __get_pydantic_core_schema__(
+ cls,
+ _source: type[Any],
+ _handler: Callable[[Any], core_schema.CoreSchema],
+ ) -> core_schema.CoreSchema:
"""Boilerplate for Pydantic."""
- yield cls.validate
+ return core_schema.general_plain_validator_function(cls.validate)
@classmethod
- def validate(cls, v: Any) -> Self:
+ def validate(cls, v: Any, _info: core_schema.ValidationInfo) -> Self:
"""Takes the given value and returns a class instance with that value."""
if isinstance(v, CustomIOField):
return cls(v.value)
diff --git a/bot/exts/filtering/filtering.py b/bot/exts/filtering/filtering.py
index 2b7ad2ff3..bedd9958f 100644
--- a/bot/exts/filtering/filtering.py
+++ b/bot/exts/filtering/filtering.py
@@ -181,7 +181,7 @@ class Filtering(Cog):
extra_fields_type,
type_hints[field_name]
)
- for field_name in extra_fields_type.__fields__
+ for field_name in extra_fields_type.model_fields
}
async def schedule_offending_messages_deletion(self) -> None:
@@ -754,7 +754,7 @@ class Filtering(Cog):
setting_values = {}
for settings_group in filter_list[list_type].defaults:
for _, setting in settings_group.items():
- setting_values.update(to_serializable(setting.dict(), ui_repr=True))
+ setting_values.update(to_serializable(setting.model_dump(), ui_repr=True))
embed = Embed(colour=Colour.blue())
populate_embed_from_dict(embed, setting_values)
@@ -1239,7 +1239,13 @@ class Filtering(Cog):
for current_settings in (filter_.actions, filter_.validations):
if current_settings:
for setting_entry in current_settings.values():
- settings.update({setting: None for setting in setting_entry.dict() if setting not in settings})
+ settings.update(
+ {
+ setting: None
+ for setting in setting_entry.model_dump()
+ if setting not in settings
+ }
+ )
# Even though the list ID remains unchanged, it still needs to be provided for correct serializer validation.
list_id = filter_list[list_type].id
@@ -1295,7 +1301,7 @@ class Filtering(Cog):
if not (differ_by_default <= override_matches): # The overrides didn't cover for the default mismatches.
return False
- filter_settings = filter_.extra_fields.dict() if filter_.extra_fields else {}
+ filter_settings = filter_.extra_fields.model_dump() if filter_.extra_fields else {}
# If the dict changes then some fields were not the same.
return (filter_settings | filter_settings_query) == filter_settings
diff --git a/bot/exts/recruitment/talentpool/_api.py b/bot/exts/recruitment/talentpool/_api.py
index e12111de5..f7b243209 100644
--- a/bot/exts/recruitment/talentpool/_api.py
+++ b/bot/exts/recruitment/talentpool/_api.py
@@ -1,6 +1,6 @@
from datetime import datetime
-from pydantic import BaseModel, Field, parse_obj_as
+from pydantic import BaseModel, Field, TypeAdapter
from pydis_core.site_api import APIClient
@@ -50,13 +50,13 @@ class NominationAPI:
params["user__id"] = str(user_id)
data = await self.site_api.get("bot/nominations", params=params)
- nominations = parse_obj_as(list[Nomination], data)
+ nominations = TypeAdapter(list[Nomination]).validate_python(data)
return nominations
async def get_nomination(self, nomination_id: int) -> Nomination:
"""Fetch a nomination by ID."""
data = await self.site_api.get(f"bot/nominations/{nomination_id}")
- nomination = Nomination.parse_obj(data)
+ nomination = Nomination.model_validate(data)
return nomination
async def edit_nomination(
@@ -84,7 +84,7 @@ class NominationAPI:
data["thread_id"] = thread_id
result = await self.site_api.patch(f"bot/nominations/{nomination_id}", json=data)
- return Nomination.parse_obj(result)
+ return Nomination.model_validate(result)
async def edit_nomination_entry(
self,
@@ -96,7 +96,7 @@ class NominationAPI:
"""Edit a nomination entry."""
data = {"actor": actor_id, "reason": reason}
result = await self.site_api.patch(f"bot/nominations/{nomination_id}", json=data)
- return Nomination.parse_obj(result)
+ return Nomination.model_validate(result)
async def post_nomination(
self,
@@ -111,7 +111,7 @@ class NominationAPI:
"user": user_id,
}
result = await self.site_api.post("bot/nominations", json=data)
- return Nomination.parse_obj(result)
+ return Nomination.model_validate(result)
async def get_activity(
self,
diff --git a/botstrap.py b/botstrap.py
index c57a254a3..7a9d94d8b 100644
--- a/botstrap.py
+++ b/botstrap.py
@@ -176,7 +176,7 @@ with DiscordClient(guild_id=GUILD_ID) as discord_client:
all_roles = discord_client.get_all_roles()
- for role_name in _Roles.__fields__:
+ for role_name in _Roles.model_fields:
role_id = all_roles.get(role_name, None)
if not role_id:
@@ -209,7 +209,7 @@ with DiscordClient(guild_id=GUILD_ID) as discord_client:
python_help_channel_id = discord_client.create_forum_channel(python_help_channel_name, python_help_category_id)
all_channels[PYTHON_HELP_CHANNEL_NAME] = python_help_channel_id
- for channel_name in _Channels.__fields__:
+ for channel_name in _Channels.model_fields:
channel_id = all_channels.get(channel_name, None)
if not channel_id:
log.warning(
@@ -222,7 +222,7 @@ with DiscordClient(guild_id=GUILD_ID) as discord_client:
config_str += "\n#Categories\n"
- for category_name in _Categories.__fields__:
+ for category_name in _Categories.model_fields:
category_id = all_categories.get(category_name, None)
if not category_id:
log.warning(
diff --git a/tests/bot/exts/filtering/test_settings_entries.py b/tests/bot/exts/filtering/test_settings_entries.py
index 3ae0b5ab5..988435022 100644
--- a/tests/bot/exts/filtering/test_settings_entries.py
+++ b/tests/bot/exts/filtering/test_settings_entries.py
@@ -173,7 +173,7 @@ class FilterTests(unittest.TestCase):
result = infraction1.union(infraction2)
self.assertDictEqual(
- result.dict(),
+ result.model_dump(),
{
"infraction_type": Infraction.TIMEOUT,
"infraction_reason": "there",
@@ -206,7 +206,7 @@ class FilterTests(unittest.TestCase):
result = infraction1.union(infraction2)
self.assertDictEqual(
- result.dict(),
+ result.model_dump(),
{
"infraction_type": Infraction.BAN,
"infraction_reason": "",