Skip to content

Commit

Permalink
Merge pull request #128 from metabrainz/stfu-troi
Browse files Browse the repository at this point in the history
Make troi quiet
  • Loading branch information
mayhem committed Feb 27, 2024
2 parents 546a18f + c690252 commit b7839a8
Show file tree
Hide file tree
Showing 47 changed files with 458 additions and 411 deletions.
2 changes: 0 additions & 2 deletions TODO

This file was deleted.

2 changes: 1 addition & 1 deletion tests/test_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ def test_hated_recordings_filter(self, mock_requests):
filter_element = troi.filters.HatedRecordingsFilterElement()
filter_element.set_sources(feedback_lookup)

received = filter_element.generate()
received = filter_element.generate(quiet=True)
expected = [
Recording(mbid="53969964-673a-4407-9396-3087be9245f6", listenbrainz={"score": 1}),
Recording(mbid="8e7a9ff8-c31d-4ac0-a01d-20a7fcc28c8f", listenbrainz={"score": 0})
Expand Down
99 changes: 61 additions & 38 deletions troi/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
from abc import ABC, abstractmethod
import logging
import random
from abc import ABC, abstractmethod
from typing import Dict

from troi.utils import recursively_update_dict

logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
_handler = logging.StreamHandler()
_handler.setFormatter(logging.Formatter("%(message)s"))
logger.addHandler(_handler)

DEVELOPMENT_SERVER_URL = "https://datasets.listenbrainz.org"

# Number of recordings that each patch should aim to generate
Expand All @@ -18,7 +24,6 @@ class Element(ABC):

def __init__(self, patch=None):
self.sources = []
self.logger = logging.getLogger(type(self).__name__)
self.patch = patch

def set_patch_object(self, patch):
Expand All @@ -38,19 +43,6 @@ def local_storage(self):

return self.patch.local_storage


def log(self, msg):
'''
Log a message with the info log level, which is the default for troi.
'''
self.logger.info(msg)

def debug(self, msg):
'''
Log a message with debug log level. These messages will only be shown when debugging is enabled.
'''
self.logger.debug(msg)

def set_sources(self, sources):
"""
Set the source elements for this element.
Expand All @@ -59,7 +51,7 @@ def set_sources(self, sources):
"""

if not isinstance(sources, list):
sources = [ sources ]
sources = [sources]

self.sources = sources

Expand All @@ -73,9 +65,7 @@ def set_sources(self, sources):
break

if not matched:
raise RuntimeError("Element %s cannot accept any of %s as input." %
(type(self).__name__, self.inputs()))

raise RuntimeError("Element %s cannot accept any of %s as input." % (type(self).__name__, self.inputs()))

def check(self):
"""
Expand All @@ -89,8 +79,7 @@ def check(self):
for source in self.sources:
source.check()


def generate(self):
def generate(self, quiet):
"""
Generate output from the pipeline. This should be called on
the last element in the pipeline and no where else. At the root
Expand All @@ -101,14 +90,14 @@ def generate(self):
source_lists = []
if self.sources:
for source in self.sources:
result = source.generate()
result = source.generate(quiet)
if result is None:
return None

if len(self.inputs()) > 0 and \
len(result) > 0 and type(result[0]) not in self.inputs() and \
len(source.outputs()) > 0:
raise RuntimeError("Element %s was expected to output %s, but actually output %s" %
raise RuntimeError("Element %s was expected to output %s, but actually output %s" %
(type(source).__name__, source.outputs()[0], type(result[0])))

source_lists.append(result)
Expand All @@ -117,10 +106,11 @@ def generate(self):
if items is None:
return None

if len(items) > 0 and type(items[0]) == Playlist:
print(" %-50s %d items" % (type(self).__name__[:49], len(items[0].recordings or [])))
else:
print(" %-50s %d items" % (type(self).__name__[:49], len(items or [])))
if not quiet:
if len(items) > 0 and type(items[0]) == Playlist:
logger.info(" %-50s %d items" % (type(self).__name__[:49], len(items[0].recordings or [])))
else:
logger.info(" %-50s %d items" % (type(self).__name__[:49], len(items or [])))

return items

Expand Down Expand Up @@ -160,8 +150,7 @@ def read(self, source_data_list):
read data from the pipeline, it calls read() on the last element in
the pipeline and this casues the while pipeline to generate result.
If the initializers of other objects in the pipeline are updated,
calling read() again will generate the set new. Passing True for
debug should print helpful debug statements about its progress.
calling read() again will generate the set new.
Note: This function should not be called directly by the user.
'''
Expand All @@ -180,6 +169,7 @@ class Entity(ABC):
of an artist or the listenbrainz dict might contain the BPM for a track.
How exactly these dicts will be organized is TDB.
"""

def __init__(self, ranking=None, musicbrainz=None, listenbrainz=None, acousticbrainz=None):
self.name = None
self.mbid = None
Expand Down Expand Up @@ -215,6 +205,7 @@ class Area(Entity):
"""
The class that represents an area.
"""

def __init__(self, id=id, name=None):
Entity.__init__(self)
self.name = name
Expand All @@ -228,8 +219,15 @@ class Artist(Entity):
"""
The class that represents an artist.
"""
def __init__(self, name=None, mbids=None, artist_credit_id=None, ranking=None,
musicbrainz=None, listenbrainz=None, acousticbrainz=None):

def __init__(self,
name=None,
mbids=None,
artist_credit_id=None,
ranking=None,
musicbrainz=None,
listenbrainz=None,
acousticbrainz=None):
Entity.__init__(self, ranking=ranking, musicbrainz=musicbrainz, listenbrainz=listenbrainz, acousticbrainz=acousticbrainz)
self.name = name
self.artist_credit_id = artist_credit_id
Expand All @@ -248,8 +246,8 @@ class Release(Entity):
"""
The class that represents a release.
"""
def __init__(self, name=None, mbid=None, artist=None, ranking=None,
musicbrainz=None, listenbrainz=None, acousticbrainz=None):

def __init__(self, name=None, mbid=None, artist=None, ranking=None, musicbrainz=None, listenbrainz=None, acousticbrainz=None):
Entity.__init__(self, ranking=ranking, musicbrainz=musicbrainz, listenbrainz=listenbrainz, acousticbrainz=acousticbrainz)
self.artist = artist
self.name = name
Expand All @@ -263,10 +261,22 @@ class Recording(Entity):
"""
The class that represents a recording.
"""
def __init__(self, name=None, mbid=None, msid=None, duration=None, artist=None, release=None,
ranking=None, year=None, spotify_id=None, musicbrainz=None, listenbrainz=None, acousticbrainz=None):

def __init__(self,
name=None,
mbid=None,
msid=None,
duration=None,
artist=None,
release=None,
ranking=None,
year=None,
spotify_id=None,
musicbrainz=None,
listenbrainz=None,
acousticbrainz=None):
Entity.__init__(self, ranking=ranking, musicbrainz=musicbrainz, listenbrainz=listenbrainz, acousticbrainz=acousticbrainz)
self.duration = duration # track duration in ms
self.duration = duration # track duration in ms
self.artist = artist
self.release = release
self.name = name
Expand All @@ -289,8 +299,20 @@ class Playlist(Entity):
and that filename is the suggested filename that this playlist should be saved as, if the user asked to
do that and didn't provide a different filename.
"""
def __init__(self, name=None, mbid=None, filename=None, recordings=None, description=None, ranking=None,
year=None, musicbrainz=None, listenbrainz=None, acousticbrainz=None, patch_slug=None, user_name=None,

def __init__(self,
name=None,
mbid=None,
filename=None,
recordings=None,
description=None,
ranking=None,
year=None,
musicbrainz=None,
listenbrainz=None,
acousticbrainz=None,
patch_slug=None,
user_name=None,
additional_metadata=None):
Entity.__init__(self, ranking=ranking, musicbrainz=musicbrainz, listenbrainz=listenbrainz, acousticbrainz=acousticbrainz)
self.name = name
Expand Down Expand Up @@ -323,6 +345,7 @@ class User(Entity):
"""
The class that represents a ListenBrainz user.
"""

def __init__(self, user_name=None, user_id=None):
Entity.__init__(self)
self.user_name = user_name
Expand Down
55 changes: 33 additions & 22 deletions troi/cli.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
#!/usr/bin/env python3

import logging
import sys

import click

from troi.logging_utils import set_log_level
from troi.utils import discover_patches
from troi.core import list_patches, patch_info, convert_patch_to_command
from troi.content_resolver.cli import cli as resolver_cli, db_file_check, output_playlist
Expand All @@ -11,6 +13,8 @@
from troi.content_resolver.playlist import read_jspf_playlist
from troi.local.periodic_jams_local import PeriodicJamsLocal

logger = logging.getLogger(__name__)

try:
sys.path.insert(1, ".")
import config
Expand All @@ -30,8 +34,7 @@ def cli():

@cli.command(context_settings=dict(ignore_unknown_options=True, ))
@click.argument('patch', type=str)
@click.option('--debug/--no-debug', help="Turn on/off debug statements")
@click.option('--print', '-p', 'echo', help="Show the generated playlist", required=False, is_flag=True)
@click.option('--quiet', '-q', 'quiet', help="Do no print out anything", required=False, is_flag=True)
@click.option('--save', '-s', help="Save the generated playlist", required=False, is_flag=True)
@click.option(
'--token',
Expand Down Expand Up @@ -59,26 +62,28 @@ def cli():
required=False,
multiple=True)
@click.argument('args', nargs=-1, type=click.UNPROCESSED)
def playlist(patch, debug, echo, save, token, upload, args, created_for, name, desc, min_recordings, spotify_user_id, spotify_token,
def playlist(patch, quiet, save, token, upload, args, created_for, name, desc, min_recordings, spotify_user_id, spotify_token,
spotify_url):
"""
Generate a global MBID based playlist using a patch
"""

set_log_level(quiet)
patchname = patch
patches = discover_patches()
if patchname not in patches:
print("Cannot load patch '%s'. Use the list command to get a list of available patches." % patchname, file=sys.stderr)
logger.info("Cannot load patch '%s'. Use the list command to get a list of available patches." % patchname)
return None

patch_args = {
"echo": echo,
"save": save,
"token": token,
"created_for": created_for,
"upload": upload,
"name": name,
"desc": desc,
"min_recordings": min_recordings
"min_recordings": min_recordings,
"quiet": quiet,
}
if spotify_token:
patch_args["spotify"] = {
Expand All @@ -97,15 +102,15 @@ def playlist(patch, debug, echo, save, token, upload, args, created_for, name, d
patch_args.update(context.forward(cmd))

# Create the actual patch, finally
patch = patches[patchname](patch_args, debug)
patch = patches[patchname](patch_args)
ret = patch.generate_playlist()

user_feedback = patch.user_feedback()
if len(user_feedback) > 0:
print("User feedback:")
logger.info("User feedback:")
for feedback in user_feedback:
print(f" * {feedback}")
print()
logger.info(f" * {feedback}")
logger.info("")

sys.exit(0 if ret else -1)

Expand All @@ -119,7 +124,7 @@ def list_patches_cli():

@cli.command(name="info")
@click.argument("patch", nargs=1)
def info(patch):
def info_cmd(patch):
"""Get info for a given patch"""
ret = patch_info(patch)
sys.exit(0 if ret else -1)
Expand All @@ -132,13 +137,15 @@ def info(patch):
@click.option('-m', '--save-to-m3u', required=False, help="save to specified m3u playlist")
@click.option('-j', '--save-to-jspf', required=False, help="save to specified JSPF playlist")
@click.option('-y', '--dont-ask', required=False, is_flag=True, help="save playlist without asking user")
@click.option('-q', '--quiet', 'quiet', help="Do no print out anything", required=False, is_flag=True)
@click.argument('jspf_playlist')
def resolve(db_file, threshold, upload_to_subsonic, save_to_m3u, save_to_jspf, dont_ask, jspf_playlist):
def resolve(db_file, threshold, upload_to_subsonic, save_to_m3u, save_to_jspf, dont_ask, quiet, jspf_playlist):
""" Resolve a global JSPF playlist with MusicBrainz MBIDs to files in the local collection"""
set_log_level(quiet)
db_file = db_file_check(db_file)
db = SubsonicDatabase(db_file, config)
db = SubsonicDatabase(db_file, config, quiet)
db.open()
lbrl = ListenBrainzRadioLocal()
lbrl = ListenBrainzRadioLocal(quiet)
playlist = read_jspf_playlist(jspf_playlist)
lbrl.resolve_playlist(threshold, playlist)
output_playlist(db, playlist, upload_to_subsonic, save_to_m3u, save_to_jspf, dont_ask)
Expand All @@ -151,14 +158,16 @@ def resolve(db_file, threshold, upload_to_subsonic, save_to_m3u, save_to_jspf, d
@click.option('-m', '--save-to-m3u', required=False, help="save to specified m3u playlist")
@click.option('-j', '--save-to-jspf', required=False, help="save to specified JSPF playlist")
@click.option('-y', '--dont-ask', required=False, is_flag=True, help="save playlist without asking user")
@click.option('-q', '--quiet', 'quiet', help="Do no print out anything", required=False, is_flag=True)
@click.argument('mode')
@click.argument('prompt')
def lb_radio(db_file, threshold, upload_to_subsonic, save_to_m3u, save_to_jspf, dont_ask, mode, prompt):
def lb_radio(db_file, threshold, upload_to_subsonic, save_to_m3u, save_to_jspf, dont_ask, quiet, mode, prompt):
"""Use LB Radio to create a playlist from a prompt, using a local music collection"""
set_log_level(quiet)
db_file = db_file_check(db_file)
db = SubsonicDatabase(db_file, config)
db = SubsonicDatabase(db_file, config, quiet)
db.open()
r = ListenBrainzRadioLocal()
r = ListenBrainzRadioLocal(quiet)
playlist = r.generate(mode, prompt, threshold)
try:
_ = playlist.playlists[0].recordings[0]
Expand All @@ -176,14 +185,16 @@ def lb_radio(db_file, threshold, upload_to_subsonic, save_to_m3u, save_to_jspf,
@click.option('-m', '--save-to-m3u', required=False, help="save to specified m3u playlist")
@click.option('-j', '--save-to-jspf', required=False, help="save to specified JSPF playlist")
@click.option('-y', '--dont-ask', required=False, is_flag=True, help="save playlist without asking user")
@click.option('-q', '--quiet', 'quiet', help="Do no print out anything", required=False, is_flag=True)
@click.argument('user_name')
def periodic_jams(db_file, threshold, upload_to_subsonic, save_to_m3u, save_to_jspf, dont_ask, user_name):
def periodic_jams(db_file, threshold, upload_to_subsonic, save_to_m3u, save_to_jspf, dont_ask, quiet, user_name):
"Generate a weekly jams playlist for your local collection"
set_log_level(quiet)
db_file = db_file_check(db_file)
db = SubsonicDatabase(db_file, config)
db = SubsonicDatabase(db_file, config, quiet)
db.open()

pj = PeriodicJamsLocal(user_name, threshold)
pj = PeriodicJamsLocal(user_name, threshold, quiet)
playlist = pj.generate()
try:
_ = playlist.playlists[0].recordings[0]
Expand All @@ -194,7 +205,7 @@ def periodic_jams(db_file, threshold, upload_to_subsonic, save_to_m3u, save_to_j
output_playlist(db, playlist, upload_to_subsonic, save_to_m3u, save_to_jspf, dont_ask)


@cli.command(context_settings=dict(ignore_unknown_options=True, ))
@cli.command(context_settings=dict(ignore_unknown_options=True,))
@click.argument('args', nargs=-1, type=click.UNPROCESSED)
def test(args):
"""Run unit tests"""
Expand Down
Loading

0 comments on commit b7839a8

Please sign in to comment.