Skip to content

Instantly share code, notes, and snippets.

@PhotonQuantum
Last active February 7, 2020 15:20
Show Gist options
  • Save PhotonQuantum/476ec1be137e4a5a59632ba81650e454 to your computer and use it in GitHub Desktop.
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()
@cosven
Copy link

cosven commented Feb 7, 2020

但是如果默认序列化的时候全部都是 brief 形式的话,想要生成详细信息的时候该怎么办呢

ummm,感觉是不是不会有这种情况。

@PhotonQuantum
Copy link
Author

但是如果默认序列化的时候全部都是 brief 形式的话,想要生成详细信息的时候该怎么办呢

ummm,感觉是不是不会有这种情况。

比如显示单首歌的时候,信息就很多(

emm 其实 brief 这个参数是从老代码那里继承来的

@cosven
Copy link

cosven commented Feb 7, 2020

目前 serialize 函数在经过一系列判断,最终会把参数全部传给 show_xxx 函数,所以这些函数的参数看起来基本是会等价的。

@cosven
Copy link

cosven commented Feb 7, 2020

但是如果默认序列化的时候全部都是 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 的。

@PhotonQuantum
Copy link
Author

PhotonQuantum commented Feb 7, 2020

这边的想法是 handler 扔出来的就是一个 Response 或者 dict[Response] 之类的东西,它最好没有对 show_xxx 的任何调用,而是由 serializer 来负责处理。

比如对于 fuo://<provider>/users/<uid>,最后 return Response(provider.User.get(uid), brief=False)

(啊 新消息我还没看到 我康一下 ummm

@PhotonQuantum
Copy link
Author

PhotonQuantum commented Feb 7, 2020

我理解,如果 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 这样实现起来觉得好像有点怪(

@cosven
Copy link

cosven commented Feb 7, 2020

我理解,如果 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 这样实现起来觉得好像有点怪(

感觉也不错。

@PhotonQuantum
Copy link
Author

刚新写了一个实现,保留了 Response,然后对 bare Model 做了默认处理

@cosven
Copy link

cosven commented Feb 7, 2020

刚新写了一个实现,保留了 Response,然后对 bare Model 做了默认处理

感觉代码又更漂亮了!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment