Skip to content

Commit

Permalink
library: compat with pydantic v1&v2 (#721)
Browse files Browse the repository at this point in the history
  • Loading branch information
cosven authored Jul 29, 2023
1 parent a6d8301 commit 9199a31
Show file tree
Hide file tree
Showing 6 changed files with 52 additions and 6 deletions.
4 changes: 3 additions & 1 deletion feeluown/entry_points/run.py
Original file line number Diff line number Diff line change
@@ -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()
Expand Down
2 changes: 1 addition & 1 deletion feeluown/gui/uimain/lyric.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
29 changes: 26 additions & 3 deletions feeluown/library/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand All @@ -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.
Expand Down
13 changes: 13 additions & 0 deletions feeluown/utils/patch.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# flake8: noqa

import asyncio
import sys


def patch_qeventloop():
Expand Down Expand Up @@ -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
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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={
Expand Down
8 changes: 8 additions & 0 deletions tests/library/test_model_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down

0 comments on commit 9199a31

Please sign in to comment.