diff options
author | 2023-07-25 15:21:02 +0100 | |
---|---|---|
committer | 2023-08-11 15:53:26 +0100 | |
commit | 292fba7c58b24df30f7631290bfecdbbd74da141 (patch) | |
tree | 8d29c9c4e53f795e16a2b5a6f4a77c588e205fd4 | |
parent | Use new Pydantic v2 logic for constant loading (diff) |
Replace deprecated functions with new pydantic v2 functions
-rw-r--r-- | bot/constants.py | 4 | ||||
-rw-r--r-- | bot/exts/filtering/_filter_lists/antispam.py | 2 | ||||
-rw-r--r-- | bot/exts/filtering/_filters/filter.py | 4 | ||||
-rw-r--r-- | bot/exts/filtering/_settings.py | 2 | ||||
-rw-r--r-- | bot/exts/filtering/_settings_types/actions/infraction_and_notification.py | 16 | ||||
-rw-r--r-- | bot/exts/filtering/_settings_types/actions/ping.py | 4 | ||||
-rw-r--r-- | bot/exts/filtering/_settings_types/settings_entry.py | 2 | ||||
-rw-r--r-- | bot/exts/filtering/_settings_types/validations/channel_scope.py | 4 | ||||
-rw-r--r-- | bot/exts/filtering/_ui/filter.py | 6 | ||||
-rw-r--r-- | bot/exts/filtering/_ui/filter_list.py | 2 | ||||
-rw-r--r-- | bot/exts/filtering/_utils.py | 13 | ||||
-rw-r--r-- | bot/exts/filtering/filtering.py | 14 | ||||
-rw-r--r-- | bot/exts/recruitment/talentpool/_api.py | 12 | ||||
-rw-r--r-- | botstrap.py | 6 | ||||
-rw-r--r-- | tests/bot/exts/filtering/test_settings_entries.py | 4 |
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": "", |