diff --git a/feeluown/entry_points/run.py b/feeluown/entry_points/run.py index 449104c354..495de5f988 100644 --- a/feeluown/entry_points/run.py +++ b/feeluown/entry_points/run.py @@ -1,6 +1,8 @@ -from feeluown.utils.patch import patch_janus, patch_qeventloop, patch_mutagen +from feeluown.utils.patch import patch_janus, patch_qeventloop, patch_mutagen, \ + patch_pydantic patch_janus() patch_mutagen() +patch_pydantic() try: patch_qeventloop() diff --git a/feeluown/gui/uimain/lyric.py b/feeluown/gui/uimain/lyric.py index 8fdb02fc73..504ea143fb 100644 --- a/feeluown/gui/uimain/lyric.py +++ b/feeluown/gui/uimain/lyric.py @@ -3,7 +3,7 @@ from PyQt5.QtCore import Qt, QRectF, QRect, QSize from PyQt5.QtGui import QPalette, QColor, QTextOption, QPainter, \ QKeySequence, QFont -from PyQt5.QtWidgets import QLabel, QWidget,\ +from PyQt5.QtWidgets import QLabel, QWidget, \ QVBoxLayout, QSizeGrip, QHBoxLayout, QColorDialog, \ QMenu, QAction, QFontDialog, QShortcut, QSpacerItem diff --git a/feeluown/library/models.py b/feeluown/library/models.py index f5a87a5674..b7812d0634 100644 --- a/feeluown/library/models.py +++ b/feeluown/library/models.py @@ -54,7 +54,17 @@ import time from typing import List, Optional, Tuple, Any, Union -from pydantic import ConfigDict, BaseModel as _BaseModel, PrivateAttr, field_validator +from pydantic import ConfigDict, BaseModel as _BaseModel, PrivateAttr +try: + # pydantic>=2.0 + from pydantic import field_validator + identifier_validator = field_validator('identifier', mode='before') + pydantic_version = 2 +except ImportError: + # pydantic<2.0 + from pydantic import validator + identifier_validator = validator('identifier', pre=True) + pydantic_version = 1 from feeluown.models import ModelType, ModelExistence, ModelStage, ModelFlags, AlbumType from feeluown.models import SearchType # noqa @@ -123,7 +133,20 @@ class BaseModel(_BaseModel): # Forbidding extra fields is good for debugging. The default behavior # is a little implicit. If you want to store an extra attribute on model, # use :meth:`cache_set` explicitly. - model_config = ConfigDict(from_attributes=False, extra='forbid') + + # For pydantic v2. + if pydantic_version == 2: + model_config = ConfigDict(from_attributes=False, extra='forbid') + else: + # For pydantic v1. + class Config: + # Do not use Model.from_orm to convert v1 model to v2 model + # since v1 model has too much magic. + orm_mode = False + # Forbidding extra fields is good for debugging. The default behavior + # is a little implicit. If you want to store an extra attribute on model, + # use :meth:`cache_set` explicitly. + extra = 'forbid' _cache: dict = PrivateAttr(default_factory=dict) meta: Any = ModelMeta.create() @@ -136,7 +159,7 @@ class BaseModel(_BaseModel): #: (DEPRECATED) for backward compact exists: ModelExistence = ModelExistence.unknown - @field_validator('identifier', mode='before') + @identifier_validator def int_to_str(cls, v): # Old version pydantic convert int to str implicitly. # Many plugins(such as netease) use int as indentifier during initialization. diff --git a/feeluown/utils/patch.py b/feeluown/utils/patch.py index 33722b2485..35c8145027 100644 --- a/feeluown/utils/patch.py +++ b/feeluown/utils/patch.py @@ -1,6 +1,7 @@ # flake8: noqa import asyncio +import sys def patch_qeventloop(): @@ -103,3 +104,15 @@ def decode_terminated(data, encoding, strict=True): return value, data _specs.decode_terminated = decode_terminated + + +def patch_pydantic(): + """ + For some reason, some plugins already use pydantic v2 and they only use + pydantic.v1 package. Make them compat with pydantic v1 in an hacker way :) + """ + try: + import pydantic.v1 + except ImportError: + import pydantic + sys.modules['pydantic.v1'] = pydantic diff --git a/setup.py b/setup.py index 94efc9349d..10e47d09a6 100644 --- a/setup.py +++ b/setup.py @@ -57,7 +57,7 @@ 'qasync', 'tomlkit', 'packaging', - 'pydantic>=2.0.0', + 'pydantic>=1.10', # v1 and v2 are both ok. 'mutagen>=1.37', ], extras_require={ diff --git a/tests/library/test_model_v2.py b/tests/library/test_model_v2.py index 4d999aab6f..a6491025ed 100644 --- a/tests/library/test_model_v2.py +++ b/tests/library/test_model_v2.py @@ -4,6 +4,14 @@ from feeluown.library import SongModel, BriefAlbumModel, BriefArtistModel, BriefSongModel +def test_use_pydantic_from_orm(song): + # Raise a pydantic exception. + # For pydantic v1, pydantic.ConfigError is raised. + # FOr pydantic v2, pydantic.ValidationError is raised. + with pytest.raises(Exception): + BriefSongModel.from_orm(song) + + def test_create_song_model_basic(): identifier = '1' brief_album = BriefAlbumModel(identifier='1', source='x',