spotify-genres/genres/model.py
2023-11-14 22:22:59 +01:00

144 lines
4 KiB
Python

import json
import yaml
import dataclasses
from dataclasses import dataclass
from collections import OrderedDict
from typing import Optional, Dict, List
def map_ditem(d):
if type(d) != dict:
return d
do = OrderedDict()
for k, v in d.items():
if type(v) == dict:
do[k] = map_ditem(v)
elif type(v) == list:
do[k] = [map_ditem(itm) for itm in v]
elif v is not None:
do[k] = v
return do
class ExtendedEncoder(json.JSONEncoder):
def default(self, o):
if dataclasses.is_dataclass(o):
return map_ditem(dataclasses.asdict(o))
return super().default(o)
@dataclass
class GenreMetadata:
name: str
description: Optional[str] = None
parent: Optional[str] = None
language: Optional[str] = None
country: Optional[str] = None
region: Optional[str] = None
localized_name: Optional[bool] = None
wikipedia_url: Optional[str] = None
wikidata_id: Optional[int] = None
rank: Optional[int] = None
playlists: Optional[Dict[str, str]] = None
alias: Optional[str] = None
deprecated: Optional[bool] = None
metagenre: Optional[bool] = None
# Packaged genre metadata
@dataclass
class GenreMetadataDB:
name: Optional[str] = None
parent: Optional[str] = None
language: Optional[str] = None
country: Optional[str] = None
region: Optional[str] = None
rank: Optional[int] = None
playlists: Optional[Dict[str, str]] = None
alias: Optional[str] = None
deprecated: Optional[bool] = None
metagenre: Optional[bool] = None
@classmethod
def conv(cls, md: GenreMetadata, name: Optional[str] = None):
if md.alias:
return cls(alias=md.alias)
return cls(name=name or md.name,
parent=md.parent,
language=md.language,
country=md.country,
region=md.region,
rank=md.rank,
playlists=md.playlists,
deprecated=md.deprecated,
metagenre=md.metagenre)
# Genre metadata from tree
@dataclass
class GenreMetadataTree:
id: str
name: Optional[str] = None
language: Optional[str] = None
country: Optional[str] = None
region: Optional[str] = None
rank: Optional[int] = None
playlists: Optional[Dict[str, str]] = None
metagenre: Optional[bool] = None
children: Optional[List['GenreMetadataTree']] = None
@classmethod
def conv(cls, genre_id: str, md: GenreMetadata):
if md.alias:
return cls(genre_id, alias=md.alias)
return cls(genre_id,
name=md.name,
language=md.language,
country=md.country,
region=md.region,
rank=md.rank,
playlists=md.playlists,
metagenre=md.metagenre)
def __lt__(self, other):
self.id.__lt__(other.id)
def load_genre_dict(d: dict) -> Dict[str, GenreMetadata]:
return {k: GenreMetadata(**v) for k, v in d.items()}
def store_pack_json(path, data, sort=False):
with open(path, "w") as f:
json.dump(data,
f,
sort_keys=sort,
ensure_ascii=False,
cls=ExtendedEncoder)
def store_genres_json(path, genre_data: Dict[str, GenreMetadata], indent=2):
with open(path, "w") as f:
json.dump(genre_data,
f,
sort_keys=True,
ensure_ascii=False,
indent=indent,
cls=ExtendedEncoder)
if indent:
f.write("\n")
def store_genres_yaml(path, genre_data: Dict[str, GenreMetadata]):
mapped_data = {
k: map_ditem(dataclasses.asdict(v))
for k, v in genre_data.items()
}
yaml.add_representer(
OrderedDict, lambda dumper, data: dumper.represent_mapping(
'tag:yaml.org,2002:map', data.items()))
with open(path, "w") as f:
yaml.dump(mapped_data, f, sort_keys=True, allow_unicode=True)