-
-
Save PhotonQuantum/476ec1be137e4a5a59632ba81650e454 to your computer and use it in GitHub Desktop.
import json | |
class Model: | |
pass | |
class DummyArtist(Model): | |
name: str | |
uri: str | |
def __init__(self, name): | |
self.name = name | |
self.uri = f"fuo://artist/{name}" | |
self.songs = [] | |
if name == "fripSide": | |
self.songs = [ | |
DummySong("Late in autumn", "fripSide_"), | |
DummySong("Only my railgun", "fripSide_") | |
] | |
def __repr__(self): | |
return f"<Artist {self.name}>" | |
class DummySong(Model): | |
title: str | |
artists: list | |
def __init__(self, title, artist): | |
self.title = title | |
self.artists = [DummyArtist(artist)] | |
self.uri = f"fuo://song/{title}" | |
def __repr__(self): | |
return f"<Song {self.title}>" | |
class DummyUser(Model): | |
name: str | |
def __init__(self, name): | |
self.name = name | |
self.uri = f"fuo://user/{name}" | |
def __repr__(self): | |
return f"<User {self.name}>" | |
@property | |
def playlist(self): | |
return [ | |
DummySong("Let it go", "blabla"), | |
DummySong("Only my railgun", "fripSide") | |
] | |
class Response: | |
def __init__(self, model, **options): | |
self.model = model | |
self.options = options | |
def dict_walker(indict, path=None): | |
path = path[:] if path else [] | |
if isinstance(indict, dict): | |
for key, value in indict.items(): | |
if isinstance(value, dict): | |
for d in dict_walker(value, path + [key]): | |
yield d | |
elif isinstance(value, list) or isinstance(value, tuple): | |
for i, v in enumerate(value): | |
for d in dict_walker(v, path + [key] + [i]): | |
yield d | |
else: | |
yield path + [key], value | |
else: | |
yield path, indict | |
def set_item_by_path(indict, path, value): | |
for key in path[:-1]: | |
indict = indict.setdefault(key, {}) | |
indict[path[-1]] = value | |
def render(model, serializer, **options): | |
if isinstance(model, DummyUser): | |
return serializer.render_user(model, **options) | |
if isinstance(model, DummySong): | |
return serializer.render_song(model, **options) | |
if isinstance(model, DummyArtist): | |
return serializer.render_artist(model, **options) | |
def serialize(obj, serializer): | |
is_complete = False | |
while not is_complete: | |
is_complete = True | |
for elem in dict_walker(obj): | |
path, value = elem | |
if isinstance(value, Response): | |
model = value.model | |
options = value.options | |
rendered = render(model, serializer, **options) | |
set_item_by_path(obj, path, rendered) | |
is_complete = False | |
break | |
if isinstance(value, Model): | |
model = value | |
rendered = render(model, serializer) | |
set_item_by_path(obj, path, rendered) | |
is_complete = False | |
break | |
return obj | |
class PlainSerializer: | |
@staticmethod | |
def render_artist(model, brief=True): | |
if brief: | |
return f"{model.uri} \t# {model.name}" | |
else: | |
return { | |
"provider": "blabla", | |
"identifier": "blablabla", | |
"name": model.name, | |
"songs": model.songs | |
} | |
@staticmethod | |
def render_song(model, brief=True): | |
if brief: | |
# artists = ",".join(...) | |
return f"{model.uri}\t# {model.title} - {model.artists[0].name}" | |
else: | |
return { | |
"provider": "blabla", | |
"identifier": "blablabla", | |
"title": model.title, | |
"artists": model.artists | |
# ... | |
} | |
@staticmethod | |
def render_user(model, brief=True): | |
if brief: | |
return f"{model.uri}\t# {model.name}" | |
else: | |
return { | |
"name": model.name, | |
"playlist": model.playlist | |
} | |
def __init__(self, data): | |
self._data = {"root": data} | |
def serialize(self): | |
return serialize(self._data, PlainSerializer)["root"] | |
class JsonSerializer: | |
_data: dict | |
@staticmethod | |
def render_artist(model, brief=True): | |
if brief: | |
return model.uri | |
else: | |
return {"name": model.name, "uri": model.uri, "songs": model.songs} | |
@staticmethod | |
def render_song(model, brief=True): | |
if brief: | |
return model.uri | |
else: | |
return {"name": model.title, "artists": model.artists, "uri": model.uri} | |
@staticmethod | |
def render_user(model, brief=True): | |
if brief: | |
return model.uri | |
else: | |
return {"name": model.name, "playlist": model.playlist, "uri": model.uri} | |
def __init__(self, data): | |
self._data = {"root": data} | |
def serialize(self): | |
return serialize(self._data, JsonSerializer)["root"] | |
class JsonEmitter: | |
def __init__(self, data): | |
self._data = data | |
def emit(self): | |
return json.dumps(self._data, indent=4) | |
class PlainEmitter: | |
def __init__(self, data): | |
self._data = data | |
def _emit(self): | |
for k, v in self._data.items(): | |
if isinstance(v, (str, int)): | |
yield f"{k}:\t{v}" | |
elif isinstance(v, list): | |
yield f"{k}::" | |
for item in v: | |
if isinstance(item, (str, int)): | |
yield f"\t{item}" | |
else: | |
raise TypeError | |
else: | |
raise TypeError | |
def emit(self): | |
return "\n".join(self._emit()) | |
class Dumper: | |
_serializer = None | |
_emitter = None | |
def __init__(self, data): | |
if isinstance(data, Model): | |
self._data = Response(data, brief=False) | |
else: | |
self._data = data | |
def dump(self): | |
serialized = self._serializer(self._data).serialize() | |
return self._emitter(serialized).emit() | |
class JsonDumper(Dumper): | |
_serializer = JsonSerializer | |
_emitter = JsonEmitter | |
class PlainDumper(Dumper): | |
_serializer = PlainSerializer | |
_emitter = PlainEmitter | |
def main(): | |
print("===JSON===") | |
print("---User---") | |
test_dict = DummyUser("lq") | |
print(JsonDumper(test_dict).dump()) | |
print("---Song---") | |
test_dict = DummySong("Only my railgun", "fripSide") | |
print(JsonDumper(test_dict).dump()) | |
print("---Artist---") | |
test_dict = DummyArtist("fripSide") | |
print(JsonDumper(test_dict).dump()) | |
print("===PLAIN===") | |
print("---User---") | |
test_dict = DummyUser("lq") | |
print(PlainDumper(test_dict).dump()) | |
print("---Song---") | |
test_dict = DummySong("Only my railgun", "fripSide") | |
print(PlainDumper(test_dict).dump()) | |
print("---Artist---") | |
test_dict = DummyArtist("fripSide") | |
print(PlainDumper(test_dict).dump()) | |
if __name__ == "__main__": | |
main() |
PhotonQuantum
commented
Feb 7, 2020
你指尖跃动的电光,是我一生不变的信仰...
假设 model 自带 serialize 函数,serialize
函数会不会写起来容易点
class DummySong:
def to_plain(self, brief=False):
pass
def to_json(self, brief=False):
pass
是会方便一点,但是总觉得 serialize 不该是 model 自己负责的事,有点耦合在一起的感觉?
是会方便一点,但是总觉得 serialize 不该是 model 自己负责的事,有点耦合在一起的感觉?
讲得通
serialize Response 这个,感觉有点点奇怪... ummm
serialize Response 这个,感觉有点点奇怪... ummm
嗯 这边可能还需要再琢磨一下(
具体奇怪在哪呢
感觉 serialize 接受的对象的类型理论上是个 Model,但是目前是有可能接受 Response。
@staticmethod
def show_artist(model, brief=False):
if brief:
return f"{model.uri} \t# {model.name}"
else:
songs = [Response(song, brief=True) for song in model.songs]
return {
"provider": "blabla",
"identifier": "blablabla",
"name": model.name,
"songs": songs
}
感觉这个地方可以直接 [show_song(song, brief=True) for song in songs]
?不需要用 Response 包一下了。
避免在 serialize 里面还要判断是否是个 Response
songs = [Response(song, brief=True) for song in model.songs]
感觉 serialize 接受的对象的类型理论上是个 Model,但是目前是有可能接受 Response。
现在这么包一下其实是为了把 brief=... 还有将来可能的 less=... 传进去,因为原本的 model 本身并不能表达 “这个地方应该用多少的详细度表示” 这样的信息(
感觉这个地方直接返回 songs 就好啦?不需要用 Response 包一下了。
最早的确是这么想的,然而发现了上面提到的这个问题,根本没法把 brief 的信息传进去(
如果光是 brief 还可以默认 model 都用 brief=True,但是如果以后有 less 的话必须得想办法告诉 serializer 才行
我是感觉在 show_artist 的时候,其实我们已经知道 less, brief 等信息了。
比如说,show_artist 这个例子,我们知道 songs 里面的 song 是肯定要 brief 形式的。
我是感觉在 show_artist 的时候,其实我们已经知道 less, brief 等信息了。
比如说,show_artist 这个例子,我们知道 songs 里面的 song 是肯定要 brief 形式的。
但是如果默认序列化的时候全部都是 brief 形式的话,想要生成详细信息的时候该怎么办呢(
本来我是默认所有的 model 都序列化成 brief 形式的,然后需要详细信息的时候交给 handler 里自己生成详细内容的,但是现在的设计思路是让 handler 只负责取 model,然后交给 helper(以及新的 serializer)来负责生成输出这样子。
要不传进 model 的时候就默认当成 brief,需要详细信息的时候拿 Response(或者啥别的名字)包一下?
但是如果默认序列化的时候全部都是 brief 形式的话,想要生成详细信息的时候该怎么办呢
ummm,感觉是不是不会有这种情况。
但是如果默认序列化的时候全部都是 brief 形式的话,想要生成详细信息的时候该怎么办呢
ummm,感觉是不是不会有这种情况。
比如显示单首歌的时候,信息就很多(
emm 其实 brief 这个参数是从老代码那里继承来的
目前 serialize 函数在经过一系列判断,最终会把参数全部传给 show_xxx 函数,所以这些函数的参数看起来基本是会等价的。
但是如果默认序列化的时候全部都是 brief 形式的话,想要生成详细信息的时候该怎么办呢
ummm,感觉是不是不会有这种情况。
比如显示单首歌的时候,信息就很多(
emm 其实 brief 这个参数是从老代码那里继承来的
我理解,如果 show_artist 里面调用其它 show_xxx 时,show_xxx 都会传个 brief=True 进去。
类似的 show_song 里面,在 show_artist,show_artist 也是 brief 的。
我感觉就是第一级的 show_xxx 是 brief=False, 然后二级 show_xxx 都是 brief=True 的。
这边的想法是 handler 扔出来的就是一个 Response 或者 dict[Response] 之类的东西,它最好没有对 show_xxx 的任何调用,而是由 serializer 来负责处理。
比如对于 fuo://<provider>/users/<uid>
,最后 return Response(provider.User.get(uid), brief=False)
(啊 新消息我还没看到 我康一下 ummm
我理解,如果 show_artist 里面调用其它 show_xxx 时,show_xxx 都会传个 brief=True 进去。
按照现在的实现思路,我希望 show_xxx 里面不要有对别的 show_xxx 的调用,如果有引用的需要,就直接扔出来 Response,在下一次 dict_walk 的时候被进一步解析。这样就可以在有需求的时候对一些复杂的引用关系进行处理,也可以解除各个 show_xxx 之间的耦合。
emm,我仔细想了一下,其实可以这样处理:如果 handler 那边 return 了一个裸的 Model,那这个 Model 是默认 brief=False 的,而别的情况下都是 brief=True 这样实现起来觉得好像有点怪(
我理解,如果 show_artist 里面调用其它 show_xxx 时,show_xxx 都会传个 brief=True 进去。
按照现在的实现思路,我希望 show_xxx 里面不要有对别的 show_xxx 的调用,如果有引用的需要,就直接扔出来 Response,在下一次 dict_walk 的时候被进一步解析。这样就可以在有需求的时候对一些复杂的引用关系进行处理,也可以解除各个 show_xxx 之间的耦合。
emm,我仔细想了一下,其实可以这样处理:如果 handler 那边 return 了一个裸的 Model,那这个 Model 是默认 brief=False 的,而别的情况下都是 brief=True这样实现起来觉得好像有点怪(
感觉也不错。
刚新写了一个实现,保留了 Response,然后对 bare Model 做了默认处理
刚新写了一个实现,保留了 Response,然后对 bare Model 做了默认处理
感觉代码又更漂亮了!