Skip to content

Commit

Permalink
serde: add deserializer and refactor mode serializer (#869)
Browse files Browse the repository at this point in the history
  • Loading branch information
cosven authored Aug 25, 2024
1 parent 9959097 commit 98f0215
Show file tree
Hide file tree
Showing 10 changed files with 459 additions and 294 deletions.
31 changes: 29 additions & 2 deletions feeluown/library/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,11 @@
import time
from typing import List, Optional, Tuple, Any, Union

from pydantic import ConfigDict, BaseModel as _BaseModel, PrivateAttr
from pydantic import (
ConfigDict, BaseModel as _BaseModel, PrivateAttr,
model_validator, model_serializer,
)

try:
# pydantic>=2.0
from pydantic import field_validator
Expand Down Expand Up @@ -215,6 +219,29 @@ def __getattr__(self, attr):
return getattr(self, attr[:-8])
raise

@model_validator(mode='before')
def _deserialize(cls, value):
if isinstance(value, dict):
js = value
if 'provider' in js:
js['source'] = js.pop('provider', None)
js.pop('uri', None)
js.pop('__type__', None)
return js
return value

@model_serializer(mode='wrap')
def _serialize(self, f):
from feeluown.library import reverse

js = f(self)
js.pop('meta')
js.pop('state')
js['provider'] = js['source']
js['uri'] = reverse(self)
js['__type__'] = f'feeluown.library.{self.__class__.__name__}'
return js


class BaseBriefModel(BaseModel):
"""
Expand Down Expand Up @@ -279,7 +306,7 @@ class SongModel(BriefSongModel, BaseNormalModel):
meta: Any = ModelMeta.create(ModelType.song, is_normal=True)
title: str
album: Optional[TAlbum] = None
artists: List[BriefArtistModel]
artists: List[TArtist]
duration: int # milliseconds
# A playlist can consist of multiple songs and a song can have many children.
# The differences between playlist's songs and song' children is that
Expand Down
44 changes: 31 additions & 13 deletions feeluown/serializers/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from .base import SerializerError
from .base import SerializerError, DeserializerError

# format Serializer mapping, like::
#
Expand All @@ -7,27 +7,40 @@
# 'plain': PlainSerializer
# }
_MAPPING = {}
_DE_MAPPING = {}


def register_serializer(type_, serializer_cls):
_MAPPING[type_] = serializer_cls


def _load_serializers():
register_serializer('plain', PlainSerializer)
register_serializer('json', JsonSerializer)
register_serializer('python', PythonSerializer)
def register_deserializer(type_, deserializer_cls):
_DE_MAPPING[type_] = deserializer_cls


def get_serializer(format):
def get_serializer(format_):
global _MAPPING

if not _MAPPING:
_load_serializers()
if format not in _MAPPING:
raise SerializerError("Serializer for format:{} not found".format(format))
return _MAPPING.get(format)
register_serializer('plain', PlainSerializer)
register_serializer('json', JsonSerializer)
register_serializer('python', PythonSerializer)
if format_ not in _MAPPING:
raise SerializerError(f"Serializer for format:{format_} not found")
return _MAPPING.get(format_)


def get_deserializer(format_: str):
global _DE_MAPPING

if not _DE_MAPPING:
register_deserializer('python', PythonDeserializer)
if format_ not in _DE_MAPPING:
raise DeserializerError(f"Deserializer for format:{format_} not found")
return _DE_MAPPING[format_]

def serialize(format, obj, **options):

def serialize(format_, obj, **options):
"""serialize python object defined in feeluown package
:raises SerializerError:
Expand All @@ -40,12 +53,17 @@ def serialize(format, obj, **options):
serialize('json', songs, indent=4, fetch=True)
serialize('json', providers)
"""
serializer = get_serializer(format)(**options)
serializer = get_serializer(format_)(**options)
return serializer.serialize(obj)


def deserialize(format_, obj, **options):
deserializer = get_deserializer(format_)(**options)
return deserializer.deserialize(obj)


from .base import SerializerMeta, SimpleSerializerMixin # noqa
from .plain import PlainSerializer # noqa
from .json_ import JsonSerializer # noqa
from .python import PythonSerializer # noqa
from .python import PythonSerializer, PythonDeserializer # noqa
from .objs import * # noqa
49 changes: 48 additions & 1 deletion feeluown/serializers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,17 @@ class SerializerError(Exception):
pass


class DeserializerError(Exception):
"""
this error will be raised when
* Deserializer initialization failed
* Deserializer not found
* Deserializer serialization failed
"""
pass


class Serializer:
"""Serializer abstract base class
Expand Down Expand Up @@ -62,7 +73,43 @@ def get_serializer_cls(cls, model):
# FIXME: remove me when model v2 has its own serializer
if isinstance(model, model_cls):
return serialize_cls
raise SerializerError("no serializer for {}".format(model))
raise SerializerError(f"no serializer for {type(model)}")


class Deserializer:
def __init__(self, **options):
"""
Subclass should validate and parse options by themselves, here,
we list three commonly used options.
as_line is a *format* option:
- as_line: line format of a object (mainly designed for PlainSerializer)
brief and fetch are *representation* options:
- brief: a minimum human readable representation of the object.
we hope that people can *identify which object it is* through
this representation.
For example, if an object has ten attributes, this representation
may only contain three attributes.
- fetch: if this option is specified, the attribute value
should be authoritative.
"""
self.options = copy.deepcopy(options)

def deserialize(self, obj):
deserializer_cls = self.get_deserializer_cls(obj)
return deserializer_cls(**self.options).deserialize(obj)

@classmethod
def get_deserializer_cls(cls, model):
for model_cls, serialize_cls in cls._mapping.items():
# FIXME: remove me when model v2 has its own serializer
if isinstance(model, model_cls):
return serialize_cls
raise SerializerError(f"no serializer for {type(model)}")


class SerializerMeta(type):
Expand Down
120 changes: 0 additions & 120 deletions feeluown/serializers/model_helpers.py

This file was deleted.

Loading

0 comments on commit 98f0215

Please sign in to comment.