From abe87ce90bc66ab09aea4e5ebbab75d8d950a7bf Mon Sep 17 00:00:00 2001 From: dtomlinson Date: Wed, 4 Mar 2020 01:09:23 +0000 Subject: [PATCH] adding builder for the artist search --- .../api/command_builders/__init__.py | 1 + .../api/command_builders/lyrics.py | 226 ++++++++++++++++++ src/musicbrainzapi/cli/cli.py | 2 + .../cli/commands/*REPL* [python] | 20 -- src/musicbrainzapi/cli/commands/cmd_lyrics.py | 49 ++-- 5 files changed, 244 insertions(+), 54 deletions(-) create mode 100644 src/musicbrainzapi/api/command_builders/__init__.py create mode 100644 src/musicbrainzapi/api/command_builders/lyrics.py delete mode 100644 src/musicbrainzapi/cli/commands/*REPL* [python] diff --git a/src/musicbrainzapi/api/command_builders/__init__.py b/src/musicbrainzapi/api/command_builders/__init__.py new file mode 100644 index 0000000..3bbab75 --- /dev/null +++ b/src/musicbrainzapi/api/command_builders/__init__.py @@ -0,0 +1 @@ +from . import lyrics diff --git a/src/musicbrainzapi/api/command_builders/lyrics.py b/src/musicbrainzapi/api/command_builders/lyrics.py new file mode 100644 index 0000000..27198ff --- /dev/null +++ b/src/musicbrainzapi/api/command_builders/lyrics.py @@ -0,0 +1,226 @@ +from __future__ import annotations +from abc import ABC, abstractmethod, abstractstaticmethod +from dataclasses import dataclass + +# from pprint import pprint + +import musicbrainzngs +import click + +from musicbrainzapi.api import authenticate + + +class LyricsConcreteBuilder(ABC): + """docstring for Lyrics""" + + @abstractstaticmethod + def set_useragent(): + authenticate.set_useragent() + + @property + @abstractmethod + def product(self) -> None: + pass + + @property + @abstractmethod + def artist(self) -> str: + pass + + @artist.setter + @abstractmethod + def artist(self, artist: str) -> None: + pass + + @property + @abstractmethod + def artist_id(self) -> str: + pass + + @artist_id.setter + @abstractmethod + def artist_id(self, artist_id: str) -> None: + pass + + @abstractmethod + def __init__(self) -> None: + pass + + @abstractmethod + def reset(self) -> None: + pass + + @abstractmethod + def do_search_artists(self) -> None: + pass + + @abstractmethod + def do_sort_names(self) -> None: + pass + + @abstractmethod + def get_accuracy_scores(self) -> None: + pass + + @abstractmethod + def get_top_five_results(self) -> None: + pass + + +class LyricsBuilder(LyricsConcreteBuilder): + """docstring for LyricsBuilder""" + + @staticmethod + def set_useragent() -> None: + authenticate.set_useragent() + + @property + def product(self) -> Lyrics: + product = self._product + return product + + @property + def artist(self) -> str: + return self._artist + + @artist.setter + def artist(self, artist: str) -> None: + self._artist = artist + + @property + def artist_id(self) -> str: + return self._artist_id + + @artist_id.setter + def artist_id(self, artist_id: str) -> None: + self._artist_id = artist_id + + def __init__(self) -> None: + self.reset() + + def reset(self) -> None: + self._product = Lyrics + + def do_search_artists(self) -> None: + self.musicbrainz_artists = musicbrainzngs.search_artists( + artist=self.artist + ) + return self + + def do_sort_names(self) -> None: + self._sort_names = dict( + (i.get('id'), f'{i.get("sort-name")} | {i.get("disambiguation")}') + if i.get('disambiguation') is not None + else (i.get('id'), f'{i.get("sort-name")}') + for i in self.musicbrainz_artists['artist-list'] + ) + return self + + def get_accuracy_scores(self) -> None: + self._accuracy_scores = dict( + (i.get('id'), int(i.get('ext:score', '0'))) + for i in self.musicbrainz_artists['artist-list'] + ) + return self + + def get_top_five_results(self) -> None: + self._top_five_results = dict( + (i, self._accuracy_scores.get(i)) + for i in sorted( + self._accuracy_scores, + key=self._accuracy_scores.get, + reverse=True, + )[0:5] + ) + return self + + +class LyricsClickDirector: + """docstring for LyricsClickDirector""" + + def __init__(self) -> None: + self._builder = None + + @property + def builder(self) -> LyricsBuilder: + return self._builder + + @builder.setter + def builder(self, builder: LyricsBuilder) -> None: + self._builder = builder + + def _get_initial_artists(self, artist: str) -> None: + self.builder.artist = artist + self.builder.set_useragent() + self.builder.do_search_artists() + self.builder.do_sort_names() + self.builder.get_accuracy_scores() + self.builder.get_top_five_results() + + def _confirm_final_artist(self) -> None: + artist_meta = None + for i, j in self.builder._top_five_results.items(): + artist_meta = 'Multiple' if j <= 100 else None + + if artist_meta == 'Multiple': + _position = [] + click.echo( + f'Musicbrainz found several results for {self.builder.artist[0]}' + '. Which artist/group do you want?', + ) + for i, j in zip(self.builder._top_five_results, range(1, 6)): + click.echo( + f'[{j}] {self.builder._sort_names.get(i)}' + f' ({self.builder._accuracy_scores.get(i)}% match)' + ) + _position.append(i) + chosen = int( + click.prompt( + f'Enter number', + type=click.Choice( + [ + str(i + 1) + for i in range(len(self.builder._top_five_results)) + ] + ), + ), + ) + choice = _position[chosen - 1] + # click.echo(choice) + click.echo(f'You chose {self.builder._sort_names.get(choice)}') + self._artist = self.builder._sort_names.get(choice).split('|')[0] + self._artist_id = choice + + elif artist_meta is None: + click.echo( + f'Musicbrainz did not find any results for ' + f'{self.builder.artist[0]}. Check the spelling or consider ' + 'alternative names that the artist/group may go by.' + ) + raise SystemExit() + + def _set_artist_id_on_product(self) -> None: + self.builder._product.artist_id = self._artist_id + self.builder._product.artist = self._artist + print(self.builder._product.artist_id) + print(self.builder._product.artist) + print(self.builder._product.__slots__) + + +@dataclass +class Lyrics: + """docstring for Lyrics""" + __slots__ = ['artist_id', 'artist'] + artist_id: str + artist: str + + +if __name__ == '__main__': + # director = LyricsClickDirector() + # builder = LyricsBuilder() + # director.builder = builder + # director._get_initial_artists('One Direction') + # director._confirm_final_artist() + # director._set_artist_id_on_product() + # director.builder._product + print(type(Lyrics)) diff --git a/src/musicbrainzapi/cli/cli.py b/src/musicbrainzapi/cli/cli.py index da501b0..9fcfd76 100644 --- a/src/musicbrainzapi/cli/cli.py +++ b/src/musicbrainzapi/cli/cli.py @@ -19,6 +19,7 @@ cmd_folder = os.path.abspath( os.path.join(os.path.dirname(__file__), 'commands') ) + class ComplexCLI(click.MultiCommand): def list_commands(self, ctx): rv = [] @@ -56,5 +57,6 @@ def cli(ctx, verbose, home): if home is not None: ctx.home = home + if __name__ == '__main__': cli() diff --git a/src/musicbrainzapi/cli/commands/*REPL* [python] b/src/musicbrainzapi/cli/commands/*REPL* [python] deleted file mode 100644 index ea3ab3c..0000000 --- a/src/musicbrainzapi/cli/commands/*REPL* [python] +++ /dev/null @@ -1,20 +0,0 @@ -Python 3.8.0 (default, Nov 3 2019, 22:15:08) -[Clang 10.0.1 (clang-1001.0.46.4)] on darwin -Type "help", "copyright", "credits" or "license" for more information. -(InteractiveConsole) ->>> match_check := False - File "", line 1 - match_check := False - ^ -SyntaxError: invalid syntax ->>> if match_check := False: -... print('false') - File "", line 2 - print('false') - ^ -IndentationError: expected an indented block ->>> if match_check := False: -... print(False) -... ->>> if not match_check := False: -... print(False) diff --git a/src/musicbrainzapi/cli/commands/cmd_lyrics.py b/src/musicbrainzapi/cli/commands/cmd_lyrics.py index 9f326a1..88f7883 100644 --- a/src/musicbrainzapi/cli/commands/cmd_lyrics.py +++ b/src/musicbrainzapi/cli/commands/cmd_lyrics.py @@ -5,6 +5,7 @@ import musicbrainzngs from musicbrainzapi.cli.cli import pass_environment from musicbrainzapi.api import authenticate +from musicbrainzapi.api.command_builders import lyrics class LyricsInfo: @@ -17,7 +18,7 @@ class LyricsInfo: def _search_artist(self) -> None: self.artists = musicbrainzngs.search_artists(artist=self.artist) - pprint(self.artists['artist-list']) + # pprint(self.artists['artist-list']) if self.artists.get('artist-count') == 0: self.chosen_artist = 'Null' @@ -56,12 +57,8 @@ class LyricsInfo: # Check for 100% match self.chosen_artist = None for i, j in self.top_five_results.items(): - if self.chosen_artist is None: - if j == 100: - self.chosen_artist = i - break + self.chosen_artist = 'Multiple' if j <= 100 else None - # pprint(self.sort_names.get(self.chosen_artist)) return self @@ -73,36 +70,20 @@ class CommandUtility: pass -@click.argument('path', required=False, type=click.Path(resolve_path=True)) +# @click.argument('path', required=False, type=click.Path(resolve_path=True)) +# @click.command(short_help='a test command') @click.option('--artist', '-a', required=True, multiple=True, type=str) -@click.command(short_help='a test command') +@click.command() @pass_environment -def cli(ctx, artist, path: click.Path) -> None: - # print(artist) - artist_0 = LyricsInfo(artist)._search_artist() - print(f'artist_0 = {artist_0.chosen_artist}') - if artist_0.chosen_artist is None: - - for i, j in zip(artist_0.top_five_results, range(1, 6)): - click.echo( - f'[{j}] {artist_0.sort_names.get(i)}' - f' ({artist_0._accuracy_scores.get(i)}% match)' - ) - - click.prompt( - f'We found several results for {artist[0]}, which artist/group do you want?' - ) - # implement this - elif artist_0.chosen_artist == 'Null': - click.echo(f"We didn't find any results for {artist}") - else: - click.confirm( - f'Musicbrainz a perfect match for {artist[0]} with ' - f'"{artist_0.sort_names.get(artist_0.chosen_artist)}" (100% match)' - '. Is this correct?' - ) - +def cli(ctx, artist) -> None: + director = lyrics.LyricsClickDirector() + builder = lyrics.LyricsBuilder() + director.builder = builder + director._get_initial_artists(artist) + director._confirm_final_artist() + director._set_artist_id_on_product() if __name__ == '__main__': - LyricsInfo('Queenifie')._search_artist() + # LyricsInfo('Queenifie')._search_artist() + LyricsInfo('Que')._search_artist()