Last active
February 7, 2020 15:20
-
-
Save PhotonQuantum/476ec1be137e4a5a59632ba81650e454 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
我理解,如果 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 做了默认处理
感觉代码又更漂亮了!
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
按照现在的实现思路,我希望 show_xxx 里面不要有对别的 show_xxx 的调用,如果有引用的需要,就直接扔出来 Response,在下一次 dict_walk 的时候被进一步解析。这样就可以在有需求的时候对一些复杂的引用关系进行处理,也可以解除各个 show_xxx 之间的耦合。
emm,我仔细想了一下,其实可以这样处理:如果 handler 那边 return 了一个裸的 Model,那这个 Model 是默认 brief=False 的,而别的情况下都是 brief=True这样实现起来觉得好像有点怪(