Browse Source

formattazione con black

boyska 4 years ago
parent
commit
d19b18eb3c
40 changed files with 949 additions and 811 deletions
  1. 2 0
      .flake8
  2. 16 15
      larigira/audioform_http.py
  3. 33 24
      larigira/audioform_mostrecent.py
  4. 28 21
      larigira/audioform_randomdir.py
  5. 29 23
      larigira/audioform_script.py
  6. 17 15
      larigira/audioform_static.py
  7. 18 13
      larigira/audiogen.py
  8. 12 13
      larigira/audiogen_http.py
  9. 26 20
      larigira/audiogen_mostrecent.py
  10. 9 8
      larigira/audiogen_mpdrandom.py
  11. 15 13
      larigira/audiogen_randomdir.py
  12. 35 34
      larigira/audiogen_script.py
  13. 10 8
      larigira/audiogen_static.py
  14. 5 4
      larigira/db.py
  15. 100 86
      larigira/dbadmin/__init__.py
  16. 17 19
      larigira/dbadmin/suggestions.py
  17. 4 3
      larigira/entrypoints_utils.py
  18. 59 46
      larigira/event.py
  19. 12 9
      larigira/event_manage.py
  20. 16 13
      larigira/eventutils.py
  21. 20 20
      larigira/filters/basic.py
  22. 40 23
      larigira/filters/tests/test_basic.py
  23. 6 5
      larigira/forms.py
  24. 17 17
      larigira/formutils.py
  25. 6 6
      larigira/fsutils.py
  26. 79 71
      larigira/mpc.py
  27. 8 7
      larigira/test_unused.py
  28. 11 11
      larigira/tests/test_audiogen_mostrecent.py
  29. 3 3
      larigira/tests/test_audiogen_mpdrandom.py
  30. 13 13
      larigira/tests/test_audiogen_randomdir.py
  31. 13 11
      larigira/tests/test_commonpath.py
  32. 21 15
      larigira/tests/test_db.py
  33. 6 6
      larigira/tests/test_fsutils.py
  34. 12 9
      larigira/tests/test_parented.py
  35. 47 69
      larigira/tests/test_time_every.py
  36. 2 1
      larigira/tests/test_web.py
  37. 88 64
      larigira/timeform_base.py
  38. 36 19
      larigira/timegen.py
  39. 33 34
      larigira/timegen_every.py
  40. 25 20
      larigira/unused.py

+ 2 - 0
.flake8

@@ -0,0 +1,2 @@
+[flake8]
+max-line-length=79

+ 16 - 15
larigira/audioform_http.py

@@ -3,23 +3,24 @@ from wtforms import StringField, validators, SubmitField
 
 
 
 
 class AudioForm(Form):
 class AudioForm(Form):
-    nick = StringField('Audio nick', validators=[validators.required()],
-                       description='A simple name to recognize this audio')
-    urls = StringField('URLs',
-                       validators=[validators.required()],
-                       description='URL of the file to download')
-    submit = SubmitField('Submit')
+    nick = StringField(
+        "Audio nick",
+        validators=[validators.required()],
+        description="A simple name to recognize this audio",
+    )
+    urls = StringField(
+        "URLs",
+        validators=[validators.required()],
+        description="URL of the file to download",
+    )
+    submit = SubmitField("Submit")
 
 
     def populate_from_audiospec(self, audiospec):
     def populate_from_audiospec(self, audiospec):
-        if 'nick' in audiospec:
-            self.nick.data = audiospec['nick']
-        if 'urls' in audiospec:
-            self.urls.data = ';'.join(audiospec['urls'])
+        if "nick" in audiospec:
+            self.nick.data = audiospec["nick"]
+        if "urls" in audiospec:
+            self.urls.data = ";".join(audiospec["urls"])
 
 
 
 
 def audio_receive(form):
 def audio_receive(form):
-    return {
-        'kind': 'http',
-        'nick': form.nick.data,
-        'urls': form.urls.data.split(';'),
-    }
+    return {"kind": "http", "nick": form.nick.data, "urls": form.urls.data.split(";")}

+ 33 - 24
larigira/audioform_mostrecent.py

@@ -6,40 +6,49 @@ from larigira.formutils import AutocompleteStringField
 
 
 
 
 class AudioForm(Form):
 class AudioForm(Form):
-    nick = StringField('Audio nick', validators=[validators.required()],
-                       description='A simple name to recognize this audio')
-    path = AutocompleteStringField('dl-suggested-dirs',
-                                   'Path', validators=[validators.required()],
-                                   description='Directory to pick file from')
-    maxage = StringField('Max age',
-                         validators=[validators.required()],
-                         description='in seconds, or human-readable '
-                         '(like 9w3d12h)')
-    submit = SubmitField('Submit')
+    nick = StringField(
+        "Audio nick",
+        validators=[validators.required()],
+        description="A simple name to recognize this audio",
+    )
+    path = AutocompleteStringField(
+        "dl-suggested-dirs",
+        "Path",
+        validators=[validators.required()],
+        description="Directory to pick file from",
+    )
+    maxage = StringField(
+        "Max age",
+        validators=[validators.required()],
+        description="in seconds, or human-readable " "(like 9w3d12h)",
+    )
+    submit = SubmitField("Submit")
 
 
     def validate_maxage(self, field):
     def validate_maxage(self, field):
         try:
         try:
             int(field.data)
             int(field.data)
         except ValueError:
         except ValueError:
             if timeparse(field.data) is None:
             if timeparse(field.data) is None:
-                raise ValidationError("maxage must either be a number "
-                                      "(in seconds) or a human-readable "
-                                      "string like '1h2m'  or '1d12h'")
+                raise ValidationError(
+                    "maxage must either be a number "
+                    "(in seconds) or a human-readable "
+                    "string like '1h2m'  or '1d12h'"
+                )
 
 
     def populate_from_audiospec(self, audiospec):
     def populate_from_audiospec(self, audiospec):
-        if 'nick' in audiospec:
-            self.nick.data = audiospec['nick']
-        if 'path' in audiospec:
-            self.path.data = audiospec['path']
-        if 'maxage' in audiospec:
-            self.maxage.data = audiospec['maxage']
+        if "nick" in audiospec:
+            self.nick.data = audiospec["nick"]
+        if "path" in audiospec:
+            self.path.data = audiospec["path"]
+        if "maxage" in audiospec:
+            self.maxage.data = audiospec["maxage"]
 
 
 
 
 def audio_receive(form):
 def audio_receive(form):
     return {
     return {
-        'kind': 'mostrecent',
-        'nick': form.nick.data,
-        'path': form.path.data,
-        'maxage': form.maxage.data,
-        'howmany': 1
+        "kind": "mostrecent",
+        "nick": form.nick.data,
+        "path": form.path.data,
+        "maxage": form.maxage.data,
+        "howmany": 1,
     }
     }

+ 28 - 21
larigira/audioform_randomdir.py

@@ -5,33 +5,40 @@ from larigira.formutils import AutocompleteStringField
 
 
 
 
 class Form(flask_wtf.Form):
 class Form(flask_wtf.Form):
-    nick = StringField('Audio nick', validators=[validators.required()],
-                       description='A simple name to recognize this audio')
-    path = AutocompleteStringField('dl-suggested-dirs',
-                                   'Path', validators=[validators.required()],
-                                   description='Full path to source directory')
-    howmany = IntegerField('Number', validators=[validators.optional()],
-                           default=1,
-                           description='How many songs to be picked'
-                           'from this dir; defaults to 1')
-    submit = SubmitField('Submit')
+    nick = StringField(
+        "Audio nick",
+        validators=[validators.required()],
+        description="A simple name to recognize this audio",
+    )
+    path = AutocompleteStringField(
+        "dl-suggested-dirs",
+        "Path",
+        validators=[validators.required()],
+        description="Full path to source directory",
+    )
+    howmany = IntegerField(
+        "Number",
+        validators=[validators.optional()],
+        default=1,
+        description="How many songs to be picked" "from this dir; defaults to 1",
+    )
+    submit = SubmitField("Submit")
 
 
     def populate_from_audiospec(self, audiospec):
     def populate_from_audiospec(self, audiospec):
-        if 'nick' in audiospec:
-            self.nick.data = audiospec['nick']
-        if 'paths' in audiospec:
-            self.path.data = audiospec['paths'][0]
-        if 'howmany' in audiospec:
-            self.howmany.data = audiospec['howmany']
+        if "nick" in audiospec:
+            self.nick.data = audiospec["nick"]
+        if "paths" in audiospec:
+            self.path.data = audiospec["paths"][0]
+        if "howmany" in audiospec:
+            self.howmany.data = audiospec["howmany"]
         else:
         else:
             self.howmany.data = 1
             self.howmany.data = 1
 
 
 
 
 def receive(form):
 def receive(form):
     return {
     return {
-        'kind': 'randomdir',
-        'nick': form.nick.data,
-        'paths': [form.path.data],
-        'howmany': form.howmany.data or 1
+        "kind": "randomdir",
+        "nick": form.nick.data,
+        "paths": [form.path.data],
+        "howmany": form.howmany.data or 1,
     }
     }
-

+ 29 - 23
larigira/audioform_script.py

@@ -3,38 +3,44 @@ from wtforms import StringField, validators, SubmitField, ValidationError
 
 
 from larigira.formutils import AutocompleteStringField
 from larigira.formutils import AutocompleteStringField
 
 
+
 class ScriptAudioForm(Form):
 class ScriptAudioForm(Form):
-    nick = StringField('Audio nick', validators=[validators.required()],
-                       description='A simple name to recognize this audio')
+    nick = StringField(
+        "Audio nick",
+        validators=[validators.required()],
+        description="A simple name to recognize this audio",
+    )
     name = AutocompleteStringField(
     name = AutocompleteStringField(
-        'dl-suggested-scripts',
-        'Name', validators=[validators.required()],
-        description='filename (NOT path) of the script')
-    args = StringField('Arguments',
-                       description='arguments, separated by ";"')
-    submit = SubmitField('Submit')
+        "dl-suggested-scripts",
+        "Name",
+        validators=[validators.required()],
+        description="filename (NOT path) of the script",
+    )
+    args = StringField("Arguments", description='arguments, separated by ";"')
+    submit = SubmitField("Submit")
 
 
     def populate_from_audiospec(self, audiospec):
     def populate_from_audiospec(self, audiospec):
-        if 'nick' in audiospec:
-            self.nick.data = audiospec['nick']
-        if 'name' in audiospec:
-            self.name.data = audiospec['name']
-        if 'args' in audiospec:
-            if type(audiospec['args']) is str:  # legacy compatibility
-                self.args.data = audiospec['args'].replace(' ', ';')
+        if "nick" in audiospec:
+            self.nick.data = audiospec["nick"]
+        if "name" in audiospec:
+            self.name.data = audiospec["name"]
+        if "args" in audiospec:
+            if type(audiospec["args"]) is str:  # legacy compatibility
+                self.args.data = audiospec["args"].replace(" ", ";")
             else:
             else:
-                self.args.data = ';'.join(audiospec['args'])
+                self.args.data = ";".join(audiospec["args"])
 
 
     def validate_name(self, field):
     def validate_name(self, field):
-        if '/' in field.data:
-            raise ValidationError("Name cannot have slashes: "
-                                  "it's a name, not a path")
+        if "/" in field.data:
+            raise ValidationError(
+                "Name cannot have slashes: " "it's a name, not a path"
+            )
 
 
 
 
 def scriptaudio_receive(form):
 def scriptaudio_receive(form):
     return {
     return {
-        'kind': 'script',
-        'nick': form.nick.data,
-        'name': form.name.data,
-        'args': form.args.data.split(';')
+        "kind": "script",
+        "nick": form.nick.data,
+        "name": form.name.data,
+        "args": form.args.data.split(";"),
     }
     }

+ 17 - 15
larigira/audioform_static.py

@@ -5,23 +5,25 @@ from larigira.formutils import AutocompleteStringField
 
 
 
 
 class StaticAudioForm(Form):
 class StaticAudioForm(Form):
-    nick = StringField('Audio nick', validators=[validators.required()],
-                       description='A simple name to recognize this audio')
-    path = AutocompleteStringField('dl-suggested-files',
-                                   'Path', validators=[validators.required()],
-                                   description='Full path to audio file')
-    submit = SubmitField('Submit')
+    nick = StringField(
+        "Audio nick",
+        validators=[validators.required()],
+        description="A simple name to recognize this audio",
+    )
+    path = AutocompleteStringField(
+        "dl-suggested-files",
+        "Path",
+        validators=[validators.required()],
+        description="Full path to audio file",
+    )
+    submit = SubmitField("Submit")
 
 
     def populate_from_audiospec(self, audiospec):
     def populate_from_audiospec(self, audiospec):
-        if 'nick' in audiospec:
-            self.nick.data = audiospec['nick']
-        if 'paths' in audiospec:
-            self.path.data = audiospec['paths'][0]
+        if "nick" in audiospec:
+            self.nick.data = audiospec["nick"]
+        if "paths" in audiospec:
+            self.path.data = audiospec["paths"][0]
 
 
 
 
 def staticaudio_receive(form):
 def staticaudio_receive(form):
-    return {
-        'kind': 'static',
-        'nick': form.nick.data,
-        'paths': [form.path.data]
-    }
+    return {"kind": "static", "nick": form.nick.data, "paths": [form.path.data]}

+ 18 - 13
larigira/audiogen.py

@@ -4,25 +4,30 @@ import argparse
 from .entrypoints_utils import get_one_entrypoint
 from .entrypoints_utils import get_one_entrypoint
 import json
 import json
 from logging import getLogger
 from logging import getLogger
-log = getLogger('audiogen')
+
+log = getLogger("audiogen")
 
 
 
 
 def get_audiogenerator(kind):
 def get_audiogenerator(kind):
-    '''Messes with entrypoints to return an audiogenerator function'''
-    return get_one_entrypoint('larigira.audiogenerators', kind)
+    """Messes with entrypoints to return an audiogenerator function"""
+    return get_one_entrypoint("larigira.audiogenerators", kind)
 
 
 
 
 def get_parser():
 def get_parser():
-    parser = argparse.ArgumentParser(
-        description='Generate audio and output paths')
-    parser.add_argument('audiospec', metavar='AUDIOSPEC', type=str, nargs=1,
-                        help='filename for audiospec, formatted in json')
+    parser = argparse.ArgumentParser(description="Generate audio and output paths")
+    parser.add_argument(
+        "audiospec",
+        metavar="AUDIOSPEC",
+        type=str,
+        nargs=1,
+        help="filename for audiospec, formatted in json",
+    )
     return parser
     return parser
 
 
 
 
 def read_spec(fname):
 def read_spec(fname):
     try:
     try:
-        if fname == '-':
+        if fname == "-":
             return json.load(sys.stdin)
             return json.load(sys.stdin)
         with open(fname) as buf:
         with open(fname) as buf:
             return json.load(buf)
             return json.load(buf)
@@ -32,28 +37,28 @@ def read_spec(fname):
 
 
 
 
 def check_spec(spec):
 def check_spec(spec):
-    if 'kind' not in spec:
+    if "kind" not in spec:
         yield "Missing field 'kind'"
         yield "Missing field 'kind'"
 
 
 
 
 def audiogenerate(spec):
 def audiogenerate(spec):
-    gen = get_audiogenerator(spec['kind'])
+    gen = get_audiogenerator(spec["kind"])
     return tuple(gen(spec))
     return tuple(gen(spec))
 
 
 
 
 def main():
 def main():
-    '''Main function for the "larigira-audiogen" executable'''
+    """Main function for the "larigira-audiogen" executable"""
     args = get_parser().parse_args()
     args = get_parser().parse_args()
     spec = read_spec(args.audiospec[0])
     spec = read_spec(args.audiospec[0])
     errors = tuple(check_spec(spec))
     errors = tuple(check_spec(spec))
     if errors:
     if errors:
         log.error("Errors in audiospec")
         log.error("Errors in audiospec")
         for err in errors:
         for err in errors:
-            sys.stderr.write('Error: {}\n'.format(err))
+            sys.stderr.write("Error: {}\n".format(err))
         sys.exit(1)
         sys.exit(1)
     for path in audiogenerate(spec):
     for path in audiogenerate(spec):
         print(path)
         print(path)
 
 
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     main()
     main()

+ 12 - 13
larigira/audiogen_http.py

@@ -9,40 +9,39 @@ log = logging.getLogger(__name__)
 
 
 
 
 def put(url, destdir=None, copy=False):
 def put(url, destdir=None, copy=False):
-    if url.split(':')[0] not in ('http', 'https'):
-        log.warning('Not a valid URL: %s', url)
+    if url.split(":")[0] not in ("http", "https"):
+        log.warning("Not a valid URL: %s", url)
         return None
         return None
-    ext = url.split('.')[-1]
-    if ext.lower() not in ('mp3', 'ogg', 'oga', 'wma', 'm4a'):
+    ext = url.split(".")[-1]
+    if ext.lower() not in ("mp3", "ogg", "oga", "wma", "m4a"):
         log.warning('Invalid format (%s) for "%s"', ext, url)
         log.warning('Invalid format (%s) for "%s"', ext, url)
         return None
         return None
     if not copy:
     if not copy:
         return url
         return url
     fname = posixpath.basename(urlparse(url).path)
     fname = posixpath.basename(urlparse(url).path)
     # sanitize
     # sanitize
-    fname = "".join(c for c in fname
-                    if c.isalnum() or c in list('._-')).rstrip()
-    tmp = mkstemp(suffix='.' + ext, prefix='http-%s-' % fname, dir=destdir)
+    fname = "".join(c for c in fname if c.isalnum() or c in list("._-")).rstrip()
+    tmp = mkstemp(suffix="." + ext, prefix="http-%s-" % fname, dir=destdir)
     os.close(tmp[0])
     os.close(tmp[0])
     log.info("downloading %s -> %s", url, tmp[1])
     log.info("downloading %s -> %s", url, tmp[1])
     fname, headers = urllib.request.urlretrieve(url, tmp[1])
     fname, headers = urllib.request.urlretrieve(url, tmp[1])
-    return 'file://%s' % os.path.realpath(tmp[1])
+    return "file://%s" % os.path.realpath(tmp[1])
 
 
 
 
 def generate(spec):
 def generate(spec):
-    '''
+    """
     resolves audiospec-static
     resolves audiospec-static
 
 
     Recognized argument is  "paths" (list of static paths)
     Recognized argument is  "paths" (list of static paths)
-    '''
-    if 'urls' not in spec:
+    """
+    if "urls" not in spec:
         raise ValueError("Malformed audiospec: missing 'paths'")
         raise ValueError("Malformed audiospec: missing 'paths'")
 
 
-    for url in spec['urls']:
+    for url in spec["urls"]:
         ret = put(url, copy=True)
         ret = put(url, copy=True)
         if ret is None:
         if ret is None:
             continue
             continue
         yield ret
         yield ret
 
 
 
 
-generate.description = 'Fetch audio from an URL'
+generate.description = "Fetch audio from an URL"

+ 26 - 20
larigira/audiogen_mostrecent.py

@@ -7,6 +7,7 @@ from tempfile import mkstemp
 from pytimeparse.timeparse import timeparse
 from pytimeparse.timeparse import timeparse
 
 
 from larigira.fsutils import scan_dir, shortname
 from larigira.fsutils import scan_dir, shortname
+
 log = logging.getLogger(__name__)
 log = logging.getLogger(__name__)
 
 
 
 
@@ -23,36 +24,40 @@ def recent_choose(paths, howmany, minepoch):
         if os.path.isfile(path):
         if os.path.isfile(path):
             found_files[path] = get_mtime(path)
             found_files[path] = get_mtime(path)
         elif os.path.isdir(path):
         elif os.path.isdir(path):
-            found_files.update({fname: get_mtime(fname)
-                                for fname in scan_dir(path)})
-    found_files = [(fname, mtime)
-                   for (fname, mtime) in found_files.items()
-                   if mtime >= minepoch]
+            found_files.update({fname: get_mtime(fname) for fname in scan_dir(path)})
+    found_files = [
+        (fname, mtime) for (fname, mtime) in found_files.items() if mtime >= minepoch
+    ]
 
 
-    return [fname for fname, mtime in
-            sorted(found_files, key=lambda x: x[1], reverse=True)[:howmany]]
+    return [
+        fname
+        for fname, mtime in sorted(found_files, key=lambda x: x[1], reverse=True)[
+            :howmany
+        ]
+    ]
 
 
 
 
 def generate(spec):
 def generate(spec):
-    '''
+    """
     resolves audiospec-randomdir
     resolves audiospec-randomdir
 
 
     Recognized arguments:
     Recognized arguments:
         - path [mandatory]    source dir
         - path [mandatory]    source dir
         - maxage [default=ignored]  max age of audio files to pick
         - maxage [default=ignored]  max age of audio files to pick
-    '''
-    for attr in ('path', 'maxage'):
+    """
+    for attr in ("path", "maxage"):
         if attr not in spec:
         if attr not in spec:
             raise ValueError("Malformed audiospec: missing '%s'" % attr)
             raise ValueError("Malformed audiospec: missing '%s'" % attr)
 
 
-    if spec['maxage'].strip():
+    if spec["maxage"].strip():
         try:
         try:
-            maxage = int(spec['maxage'])
+            maxage = int(spec["maxage"])
         except ValueError:
         except ValueError:
-            maxage = timeparse(spec['maxage'])
+            maxage = timeparse(spec["maxage"])
             if maxage is None:
             if maxage is None:
-                raise ValueError("Unknown format for maxage: '{}'"
-                                 .format(spec['maxage']))
+                raise ValueError(
+                    "Unknown format for maxage: '{}'".format(spec["maxage"])
+                )
             assert type(maxage) is int
             assert type(maxage) is int
     else:
     else:
         maxage = None
         maxage = None
@@ -60,15 +65,16 @@ def generate(spec):
     now = int(time.time())
     now = int(time.time())
     minepoch = 0 if maxage is None else now - maxage
     minepoch = 0 if maxage is None else now - maxage
 
 
-    picked = recent_choose([spec['path']], 1, minepoch)
+    picked = recent_choose([spec["path"]], 1, minepoch)
 
 
     for path in picked:
     for path in picked:
-        tmp = mkstemp(suffix=os.path.splitext(path)[-1],
-                      prefix='randomdir-%s-' % shortname(path))
+        tmp = mkstemp(
+            suffix=os.path.splitext(path)[-1], prefix="randomdir-%s-" % shortname(path)
+        )
         os.close(tmp[0])
         os.close(tmp[0])
         shutil.copy(path, tmp[1])
         shutil.copy(path, tmp[1])
         log.info("copying %s -> %s", path, os.path.basename(tmp[1]))
         log.info("copying %s -> %s", path, os.path.basename(tmp[1]))
-        yield 'file://{}'.format(tmp[1])
+        yield "file://{}".format(tmp[1])
 
 
 
 
-generate.description = 'Select most recent file inside a directory'
+generate.description = "Select most recent file inside a directory"

+ 9 - 8
larigira/audiogen_mpdrandom.py

@@ -1,5 +1,6 @@
 import logging
 import logging
-log = logging.getLogger('mpdrandom')
+
+log = logging.getLogger("mpdrandom")
 import random
 import random
 
 
 from mpd import MPDClient
 from mpd import MPDClient
@@ -8,17 +9,17 @@ from .config import get_conf
 
 
 
 
 def generate_by_artist(spec):
 def generate_by_artist(spec):
-    '''choose HOWMANY random artists, and for each one choose a random song'''
-    spec.setdefault('howmany', 1)
-    log.info('generating')
+    """choose HOWMANY random artists, and for each one choose a random song"""
+    spec.setdefault("howmany", 1)
+    log.info("generating")
     conf = get_conf()
     conf = get_conf()
     c = MPDClient(use_unicode=True)
     c = MPDClient(use_unicode=True)
-    c.connect(conf['MPD_HOST'], conf['MPD_PORT'])
+    c.connect(conf["MPD_HOST"], conf["MPD_PORT"])
 
 
-    artists = c.list('artist')
+    artists = c.list("artist")
     log.debug("got %d artists", len(artists))
     log.debug("got %d artists", len(artists))
     if not artists:
     if not artists:
         raise ValueError("no artists in your mpd database")
         raise ValueError("no artists in your mpd database")
-    for _ in range(spec['howmany']):
+    for _ in range(spec["howmany"]):
         artist = random.choice(artists)
         artist = random.choice(artists)
-        yield random.choice(c.find('artist', artist))['file']
+        yield random.choice(c.find("artist", artist))["file"]

+ 15 - 13
larigira/audiogen_randomdir.py

@@ -6,6 +6,7 @@ from tempfile import mkstemp
 from pathlib import Path
 from pathlib import Path
 
 
 from larigira.fsutils import scan_dir_audio, shortname, is_audio
 from larigira.fsutils import scan_dir_audio, shortname, is_audio
+
 log = logging.getLogger(__name__)
 log = logging.getLogger(__name__)
 
 
 
 
@@ -23,36 +24,37 @@ def candidates(paths):
 
 
 
 
 def generate(spec):
 def generate(spec):
-    '''
+    """
     resolves audiospec-randomdir
     resolves audiospec-randomdir
 
 
     Recognized arguments:
     Recognized arguments:
         - paths [mandatory]    list of source paths
         - paths [mandatory]    list of source paths
         - howmany [default=1]  number of audio files to pick
         - howmany [default=1]  number of audio files to pick
-    '''
-    spec.setdefault('howmany', 1)
-    for attr in ('paths', ):
+    """
+    spec.setdefault("howmany", 1)
+    for attr in ("paths",):
         if attr not in spec:
         if attr not in spec:
             raise ValueError("Malformed audiospec: missing '%s'" % attr)
             raise ValueError("Malformed audiospec: missing '%s'" % attr)
 
 
-    found_files = candidates([Path(p) for p in spec['paths']])
+    found_files = candidates([Path(p) for p in spec["paths"]])
 
 
-    picked = random.sample(found_files, int(spec['howmany']))
+    picked = random.sample(found_files, int(spec["howmany"]))
 
 
-    nick = spec.get('nick', '')
+    nick = spec.get("nick", "")
     if not nick:
     if not nick:
-        if hasattr(spec, 'eid'):
+        if hasattr(spec, "eid"):
             nick = spec.eid
             nick = spec.eid
         else:
         else:
-            nick = 'NONICK'
+            nick = "NONICK"
     for path in picked:
     for path in picked:
-        tmp = mkstemp(suffix=os.path.splitext(path)[-1],
-                      prefix='randomdir-%s-%s-' % (shortname(nick),
-                                                   shortname(path)))
+        tmp = mkstemp(
+            suffix=os.path.splitext(path)[-1],
+            prefix="randomdir-%s-%s-" % (shortname(nick), shortname(path)),
+        )
         os.close(tmp[0])
         os.close(tmp[0])
         shutil.copy(path, tmp[1])
         shutil.copy(path, tmp[1])
         log.info("copying %s -> %s", path, os.path.basename(tmp[1]))
         log.info("copying %s -> %s", path, os.path.basename(tmp[1]))
         yield "file://{}".format(tmp[1])
         yield "file://{}".format(tmp[1])
 
 
 
 
-generate.description = 'Picks random files from a specified directory'
+generate.description = "Picks random files from a specified directory"

+ 35 - 34
larigira/audiogen_script.py

@@ -1,4 +1,4 @@
-'''
+"""
 script audiogenerator: uses an external program to generate audio URIs
 script audiogenerator: uses an external program to generate audio URIs
 
 
 a script can be any valid executable in
 a script can be any valid executable in
@@ -14,64 +14,65 @@ The output MUST be UTF-8-encoded.
 Empty lines will be skipped.  stderr will be logged, so please be careful.  any
 Empty lines will be skipped.  stderr will be logged, so please be careful.  any
 non-zero exit code will result in no files being added.and an exception being
 non-zero exit code will result in no files being added.and an exception being
 logged.
 logged.
-'''
+"""
 import logging
 import logging
 import os
 import os
 import subprocess
 import subprocess
 
 
 from .config import get_conf
 from .config import get_conf
+
 log = logging.getLogger(__name__)
 log = logging.getLogger(__name__)
 
 
 
 
 def generate(spec):
 def generate(spec):
-    '''
+    """
     Recognized arguments (fields in spec):
     Recognized arguments (fields in spec):
         - name [mandatory]            script name
         - name [mandatory]            script name
         - args [default=empty]   arguments, colon-separated
         - args [default=empty]   arguments, colon-separated
-    '''
+    """
     conf = get_conf()
     conf = get_conf()
-    spec.setdefault('args', '')
-    if type(spec['args']) is str:
-        args = spec['args'].split(';')
-    args = list(spec['args'])
-    for attr in ('name', ):
+    spec.setdefault("args", "")
+    if type(spec["args"]) is str:
+        args = spec["args"].split(";")
+    args = list(spec["args"])
+    for attr in ("name",):
         if attr not in spec:
         if attr not in spec:
             raise ValueError("Malformed audiospec: missing '%s'" % attr)
             raise ValueError("Malformed audiospec: missing '%s'" % attr)
 
 
-    if '/' in spec['name']:
-        raise ValueError("Script name is a filename, not a path ({} provided)"
-                         .format(spec['name']))
-    scriptpath = os.path.join(conf['SCRIPTS_PATH'], spec['name'])
+    if "/" in spec["name"]:
+        raise ValueError(
+            "Script name is a filename, not a path ({} provided)".format(spec["name"])
+        )
+    scriptpath = os.path.join(conf["SCRIPTS_PATH"], spec["name"])
     if not os.path.exists(scriptpath):
     if not os.path.exists(scriptpath):
-        raise ValueError("Script %s not found", spec['name'])
+        raise ValueError("Script %s not found", spec["name"])
     if not os.access(scriptpath, os.R_OK | os.X_OK):
     if not os.access(scriptpath, os.R_OK | os.X_OK):
         raise ValueError("Insufficient privileges for script %s" % scriptpath)
         raise ValueError("Insufficient privileges for script %s" % scriptpath)
 
 
     if os.stat(scriptpath).st_uid != os.getuid():
     if os.stat(scriptpath).st_uid != os.getuid():
-        raise ValueError("Script %s owned by %d, should be owned by %d"
-                         % (spec['name'], os.stat(scriptpath).st_uid,
-                            os.getuid()))
+        raise ValueError(
+            "Script %s owned by %d, should be owned by %d"
+            % (spec["name"], os.stat(scriptpath).st_uid, os.getuid())
+        )
     try:
     try:
-        log.info('Going to run %s', [scriptpath] + args)
+        log.info("Going to run %s", [scriptpath] + args)
         env = dict(
         env = dict(
-            HOME=os.environ['HOME'],
-            PATH=os.environ['PATH'],
-            MPD_HOST=conf['MPD_HOST'],
-            MPD_PORT=str(conf['MPD_PORT'])
+            HOME=os.environ["HOME"],
+            PATH=os.environ["PATH"],
+            MPD_HOST=conf["MPD_HOST"],
+            MPD_PORT=str(conf["MPD_PORT"]),
         )
         )
-        if 'TMPDIR' in os.environ:
-            env['TMPDIR'] = os.environ['TMPDIR']
-        out = subprocess.check_output([scriptpath] + args,
-                                      env=env,
-                                      cwd='/')
+        if "TMPDIR" in os.environ:
+            env["TMPDIR"] = os.environ["TMPDIR"]
+        out = subprocess.check_output([scriptpath] + args, env=env, cwd="/")
     except subprocess.CalledProcessError as exc:
     except subprocess.CalledProcessError as exc:
-        log.error("Error %d when running script %s",
-                  exc.returncode, spec['name'])
+        log.error("Error %d when running script %s", exc.returncode, spec["name"])
         return []
         return []
 
 
-    out = out.decode('utf-8')
-    out = [p for p in out.split('\n') if p]
-    logging.debug('Script %s produced %d files', spec['name'], len(out))
+    out = out.decode("utf-8")
+    out = [p for p in out.split("\n") if p]
+    logging.debug("Script %s produced %d files", spec["name"], len(out))
     return out
     return out
-generate.description = 'Generate audio through an external script. ' \
-'Experts only.'
+
+
+generate.description = "Generate audio through an external script. " "Experts only."

+ 10 - 8
larigira/audiogen_static.py

@@ -4,28 +4,30 @@ import shutil
 from tempfile import mkstemp
 from tempfile import mkstemp
 
 
 from larigira.fsutils import shortname
 from larigira.fsutils import shortname
+
 log = logging.getLogger(__name__)
 log = logging.getLogger(__name__)
 
 
 
 
 def generate(spec):
 def generate(spec):
-    '''
+    """
     resolves audiospec-static
     resolves audiospec-static
 
 
     Recognized argument is  "paths" (list of static paths)
     Recognized argument is  "paths" (list of static paths)
-    '''
-    if 'paths' not in spec:
+    """
+    if "paths" not in spec:
         raise ValueError("Malformed audiospec: missing 'paths'")
         raise ValueError("Malformed audiospec: missing 'paths'")
 
 
-    for path in spec['paths']:
+    for path in spec["paths"]:
         if not os.path.exists(path):
         if not os.path.exists(path):
             log.warning("Can't find requested path: %s", path)
             log.warning("Can't find requested path: %s", path)
             continue
             continue
-        tmp = mkstemp(suffix=os.path.splitext(path)[-1],
-                      prefix='static-%s-' % shortname(path))
+        tmp = mkstemp(
+            suffix=os.path.splitext(path)[-1], prefix="static-%s-" % shortname(path)
+        )
         os.close(tmp[0])
         os.close(tmp[0])
         log.info("copying %s -> %s", path, os.path.basename(tmp[1]))
         log.info("copying %s -> %s", path, os.path.basename(tmp[1]))
         shutil.copy(path, tmp[1])
         shutil.copy(path, tmp[1])
-        yield 'file://{}'.format(tmp[1])
+        yield "file://{}".format(tmp[1])
 
 
 
 
-generate.description = 'Picks always the same specified file'
+generate.description = "Picks always the same specified file"

+ 5 - 4
larigira/db.py

@@ -1,5 +1,6 @@
 from tinydb import TinyDB
 from tinydb import TinyDB
 
 
+
 class EventModel(object):
 class EventModel(object):
     def __init__(self, uri):
     def __init__(self, uri):
         self.uri = uri
         self.uri = uri
@@ -10,8 +11,8 @@ class EventModel(object):
         if self.db is not None:
         if self.db is not None:
             self.db.close()
             self.db.close()
         self.db = TinyDB(self.uri, indent=2)
         self.db = TinyDB(self.uri, indent=2)
-        self._actions = self.db.table('actions')
-        self._alarms = self.db.table('alarms')
+        self._actions = self.db.table("actions")
+        self._alarms = self.db.table("alarms")
 
 
     def get_action_by_id(self, action_id):
     def get_action_by_id(self, action_id):
         return self._actions.get(eid=action_id)
         return self._actions.get(eid=action_id)
@@ -20,7 +21,7 @@ class EventModel(object):
         return self._alarms.get(eid=alarm_id)
         return self._alarms.get(eid=alarm_id)
 
 
     def get_actions_by_alarm(self, alarm):
     def get_actions_by_alarm(self, alarm):
-        for action_id in alarm.get('actions', []):
+        for action_id in alarm.get("actions", []):
             action = self.get_action_by_id(action_id)
             action = self.get_action_by_id(action_id)
             if action is None:
             if action is None:
                 continue
                 continue
@@ -39,7 +40,7 @@ class EventModel(object):
 
 
     def add_event(self, alarm, actions):
     def add_event(self, alarm, actions):
         action_ids = [self.add_action(a) for a in actions]
         action_ids = [self.add_action(a) for a in actions]
-        alarm['actions'] = action_ids
+        alarm["actions"] = action_ids
         return self._alarms.insert(alarm)
         return self._alarms.insert(alarm)
 
 
     def add_action(self, action):
     def add_action(self, action):

+ 100 - 86
larigira/dbadmin/__init__.py

@@ -1,14 +1,23 @@
-'''
+"""
 This module contains a flask blueprint for db administration stuff
 This module contains a flask blueprint for db administration stuff
 
 
 Templates are self-contained in this directory
 Templates are self-contained in this directory
-'''
+"""
 from __future__ import print_function
 from __future__ import print_function
 from datetime import datetime, timedelta, time
 from datetime import datetime, timedelta, time
 from collections import defaultdict
 from collections import defaultdict
 
 
-from flask import current_app, Blueprint, render_template, jsonify, abort, \
-    request, redirect, url_for, flash
+from flask import (
+    current_app,
+    Blueprint,
+    render_template,
+    jsonify,
+    abort,
+    request,
+    redirect,
+    url_for,
+    flash,
+)
 
 
 from larigira.entrypoints_utils import get_avail_entrypoints
 from larigira.entrypoints_utils import get_avail_entrypoints
 from larigira.audiogen import get_audiogenerator
 from larigira.audiogen import get_audiogenerator
@@ -17,56 +26,63 @@ from larigira.timegen import get_timegenerator, timegenerate
 from larigira import forms
 from larigira import forms
 from larigira.config import get_conf
 from larigira.config import get_conf
 from .suggestions import get_suggestions
 from .suggestions import get_suggestions
-db = Blueprint('db', __name__,
-               url_prefix=get_conf()['ROUTE_PREFIX'] + '/db',
-               template_folder='templates')
+
+db = Blueprint(
+    "db",
+    __name__,
+    url_prefix=get_conf()["ROUTE_PREFIX"] + "/db",
+    template_folder="templates",
+)
 
 
 
 
 def request_wants_json():
 def request_wants_json():
-    best = request.accept_mimetypes \
-        .best_match(['application/json', 'text/html'])
-    return best == 'application/json' and \
-        request.accept_mimetypes[best] > \
-        request.accept_mimetypes['text/html']
+    best = request.accept_mimetypes.best_match(["application/json", "text/html"])
+    return (
+        best == "application/json"
+        and request.accept_mimetypes[best] > request.accept_mimetypes["text/html"]
+    )
 
 
 
 
 def get_model():
 def get_model():
     return current_app.larigira.controller.monitor.model
     return current_app.larigira.controller.monitor.model
 
 
 
 
-@db.route('/')
+@db.route("/")
 def home():
 def home():
-    return render_template('dbadmin_base.html')
+    return render_template("dbadmin_base.html")
 
 
 
 
-@db.route('/list')
+@db.route("/list")
 def events_list():
 def events_list():
     model = current_app.larigira.controller.monitor.model
     model = current_app.larigira.controller.monitor.model
     alarms = tuple(model.get_all_alarms())
     alarms = tuple(model.get_all_alarms())
-    events = [(alarm, model.get_actions_by_alarm(alarm))
-              for alarm in alarms]
-    return render_template('list.html', events=events)
+    events = [(alarm, model.get_actions_by_alarm(alarm)) for alarm in alarms]
+    return render_template("list.html", events=events)
 
 
 
 
-@db.route('/calendar')
+@db.route("/calendar")
 def events_calendar():
 def events_calendar():
     model = current_app.larigira.controller.monitor.model
     model = current_app.larigira.controller.monitor.model
     today = datetime.now().date()
     today = datetime.now().date()
     maxdays = 30
     maxdays = 30
     # {date: {datetime: [(alarm1,actions1), (alarm2,actions2)]}}
     # {date: {datetime: [(alarm1,actions1), (alarm2,actions2)]}}
     days = defaultdict(lambda: defaultdict(list))
     days = defaultdict(lambda: defaultdict(list))
-    freq_threshold = get_conf()['UI_CALENDAR_FREQUENCY_THRESHOLD']
+    freq_threshold = get_conf()["UI_CALENDAR_FREQUENCY_THRESHOLD"]
     for alarm in model.get_all_alarms():
     for alarm in model.get_all_alarms():
-        if freq_threshold and alarm['kind'] == 'frequency' and \
-           FrequencyAlarm(alarm).interval < freq_threshold:
+        if (
+            freq_threshold
+            and alarm["kind"] == "frequency"
+            and FrequencyAlarm(alarm).interval < freq_threshold
+        ):
             continue
             continue
         actions = tuple(model.get_actions_by_alarm(alarm))
         actions = tuple(model.get_actions_by_alarm(alarm))
         if not actions:
         if not actions:
             continue
             continue
-        t = datetime.fromtimestamp(int(today.strftime('%s')))
+        t = datetime.fromtimestamp(int(today.strftime("%s")))
         for t in timegenerate(alarm, now=t, howmany=maxdays):
         for t in timegenerate(alarm, now=t, howmany=maxdays):
-            if t is None or \
-               t > datetime.combine(today+timedelta(days=maxdays), time()):
+            if t is None or t > datetime.combine(
+                today + timedelta(days=maxdays), time()
+            ):
                 break
                 break
             days[t.date()][t].append((alarm, actions))
             days[t.date()][t].append((alarm, actions))
 
 
@@ -75,105 +91,101 @@ def events_calendar():
     for d in sorted(days.keys()):
     for d in sorted(days.keys()):
         weeks[d.isocalendar()[:2]].append(d)
         weeks[d.isocalendar()[:2]].append(d)
 
 
-    return render_template('calendar.html', days=days, weeks=weeks)
+    return render_template("calendar.html", days=days, weeks=weeks)
 
 
 
 
-@db.route('/add/time')
+@db.route("/add/time")
 def addtime():
 def addtime():
-    kinds = get_avail_entrypoints('larigira.timeform_create')
+    kinds = get_avail_entrypoints("larigira.timeform_create")
 
 
     def gen_info(gen):
     def gen_info(gen):
-        return dict(description=getattr(gen, 'description', ''))
-    info = {kind: gen_info(get_timegenerator(kind))
-            for kind in kinds}
-    return render_template('add_time.html', kinds=kinds, info=info)
+        return dict(description=getattr(gen, "description", ""))
+
+    info = {kind: gen_info(get_timegenerator(kind)) for kind in kinds}
+    return render_template("add_time.html", kinds=kinds, info=info)
 
 
 
 
-@db.route('/edit/time/<int:alarmid>', methods=['GET', 'POST'])
+@db.route("/edit/time/<int:alarmid>", methods=["GET", "POST"])
 def edit_time(alarmid):
 def edit_time(alarmid):
     model = get_model()
     model = get_model()
     timespec = model.get_alarm_by_id(alarmid)
     timespec = model.get_alarm_by_id(alarmid)
-    kind = timespec['kind']
+    kind = timespec["kind"]
     Form, receiver = tuple(forms.get_timeform(kind))
     Form, receiver = tuple(forms.get_timeform(kind))
     form = Form()
     form = Form()
-    if request.method == 'GET':
+    if request.method == "GET":
         form.populate_from_timespec(timespec)
         form.populate_from_timespec(timespec)
-    if request.method == 'POST' and form.validate():
+    if request.method == "POST" and form.validate():
         data = receiver(form)
         data = receiver(form)
         model.update_alarm(alarmid, data)
         model.update_alarm(alarmid, data)
         model.reload()
         model.reload()
-        return redirect(url_for('db.events_list',
-                                _anchor='event-%d' % alarmid))
-    return render_template('add_time_kind.html',
-                           form=form,
-                           kind=kind,
-                           mode='edit',
-                           alarmid=alarmid,
-                           )
+        return redirect(url_for("db.events_list", _anchor="event-%d" % alarmid))
+    return render_template(
+        "add_time_kind.html", form=form, kind=kind, mode="edit", alarmid=alarmid
+    )
 
 
 
 
-@db.route('/add/time/<kind>', methods=['GET', 'POST'])
+@db.route("/add/time/<kind>", methods=["GET", "POST"])
 def addtime_kind(kind):
 def addtime_kind(kind):
     Form, receiver = tuple(forms.get_timeform(kind))
     Form, receiver = tuple(forms.get_timeform(kind))
     form = Form()
     form = Form()
-    if request.method == 'POST' and form.validate():
+    if request.method == "POST" and form.validate():
         data = receiver(form)
         data = receiver(form)
         eid = get_model().add_alarm(data)
         eid = get_model().add_alarm(data)
-        return redirect(url_for('db.edit_event', alarmid=eid))
+        return redirect(url_for("db.edit_event", alarmid=eid))
 
 
-    return render_template('add_time_kind.html',
-                           form=form, kind=kind, mode='add')
+    return render_template("add_time_kind.html", form=form, kind=kind, mode="add")
 
 
 
 
-@db.route('/add/audio')
+@db.route("/add/audio")
 def addaudio():
 def addaudio():
-    kinds = get_avail_entrypoints('larigira.audioform_create')
+    kinds = get_avail_entrypoints("larigira.audioform_create")
 
 
     def gen_info(gen):
     def gen_info(gen):
-        return dict(description=getattr(gen, 'description', ''))
-    info = {kind: gen_info(get_audiogenerator(kind))
-            for kind in kinds}
-    return render_template('add_audio.html', kinds=kinds, info=info)
+        return dict(description=getattr(gen, "description", ""))
+
+    info = {kind: gen_info(get_audiogenerator(kind)) for kind in kinds}
+    return render_template("add_audio.html", kinds=kinds, info=info)
 
 
 
 
-@db.route('/add/audio/<kind>', methods=['GET', 'POST'])
+@db.route("/add/audio/<kind>", methods=["GET", "POST"])
 def addaudio_kind(kind):
 def addaudio_kind(kind):
     Form, receiver = tuple(forms.get_audioform(kind))
     Form, receiver = tuple(forms.get_audioform(kind))
     form = Form()
     form = Form()
-    if request.method == 'POST' and form.validate():
+    if request.method == "POST" and form.validate():
         data = receiver(form)
         data = receiver(form)
         model = current_app.larigira.controller.monitor.model
         model = current_app.larigira.controller.monitor.model
         eid = model.add_action(data)
         eid = model.add_action(data)
         return jsonify(dict(inserted=eid, data=data))
         return jsonify(dict(inserted=eid, data=data))
 
 
-    return render_template('add_audio_kind.html', form=form, kind=kind,
-                           suggestions=get_suggestions()
-                           )
+    return render_template(
+        "add_audio_kind.html", form=form, kind=kind, suggestions=get_suggestions()
+    )
 
 
 
 
-@db.route('/edit/audio/<int:actionid>', methods=['GET', 'POST'])
+@db.route("/edit/audio/<int:actionid>", methods=["GET", "POST"])
 def edit_audio(actionid):
 def edit_audio(actionid):
     model = get_model()
     model = get_model()
     audiospec = model.get_action_by_id(actionid)
     audiospec = model.get_action_by_id(actionid)
-    kind = audiospec['kind']
+    kind = audiospec["kind"]
     Form, receiver = tuple(forms.get_audioform(kind))
     Form, receiver = tuple(forms.get_audioform(kind))
     form = Form()
     form = Form()
-    if request.method == 'GET':
+    if request.method == "GET":
         form.populate_from_audiospec(audiospec)
         form.populate_from_audiospec(audiospec)
-    if request.method == 'POST' and form.validate():
+    if request.method == "POST" and form.validate():
         data = receiver(form)
         data = receiver(form)
         model.update_action(actionid, data)
         model.update_action(actionid, data)
         model.reload()
         model.reload()
-        return redirect(url_for('db.events_list'))
-    return render_template('add_audio_kind.html',
-                           form=form,
-                           kind=kind,
-                           mode='edit',
-                           suggestions=get_suggestions()
-                           )
+        return redirect(url_for("db.events_list"))
+    return render_template(
+        "add_audio_kind.html",
+        form=form,
+        kind=kind,
+        mode="edit",
+        suggestions=get_suggestions(),
+    )
 
 
 
 
-@db.route('/edit/event/<alarmid>')
+@db.route("/edit/event/<alarmid>")
 def edit_event(alarmid):
 def edit_event(alarmid):
     model = current_app.larigira.controller.monitor.model
     model = current_app.larigira.controller.monitor.model
     alarm = model.get_alarm_by_id(int(alarmid))
     alarm = model.get_alarm_by_id(int(alarmid))
@@ -181,35 +193,37 @@ def edit_event(alarmid):
         abort(404)
         abort(404)
     allactions = model.get_all_actions()
     allactions = model.get_all_actions()
     actions = tuple(model.get_actions_by_alarm(alarm))
     actions = tuple(model.get_actions_by_alarm(alarm))
-    return render_template('edit_event.html',
-                           alarm=alarm, all_actions=allactions,
-                           actions=actions,
-                           routeprefix=get_conf()['ROUTE_PREFIX']
-                          )
+    return render_template(
+        "edit_event.html",
+        alarm=alarm,
+        all_actions=allactions,
+        actions=actions,
+        routeprefix=get_conf()["ROUTE_PREFIX"],
+    )
 
 
 
 
-@db.route('/api/alarm/<alarmid>/actions', methods=['POST'])
+@db.route("/api/alarm/<alarmid>/actions", methods=["POST"])
 def change_actions(alarmid):
 def change_actions(alarmid):
-    new_actions = request.form.getlist('actions[]')
+    new_actions = request.form.getlist("actions[]")
     if new_actions is None:
     if new_actions is None:
         new_actions = []
         new_actions = []
     model = current_app.larigira.controller.monitor.model
     model = current_app.larigira.controller.monitor.model
-    ret = model.update_alarm(int(alarmid),
-                             new_fields={'actions': [int(a) for a in
-                                                     new_actions]})
+    ret = model.update_alarm(
+        int(alarmid), new_fields={"actions": [int(a) for a in new_actions]}
+    )
     return jsonify(dict(updated=alarmid, ret=ret))
     return jsonify(dict(updated=alarmid, ret=ret))
 
 
 
 
-@db.route('/api/alarm/<int:alarmid>/delete', methods=['POST'])
+@db.route("/api/alarm/<int:alarmid>/delete", methods=["POST"])
 def delete_alarm(alarmid):
 def delete_alarm(alarmid):
     model = current_app.larigira.controller.monitor.model
     model = current_app.larigira.controller.monitor.model
     try:
     try:
         alarm = model.get_alarm_by_id(int(alarmid))
         alarm = model.get_alarm_by_id(int(alarmid))
-        print(alarm['nick'])
+        print(alarm["nick"])
         model.delete_alarm(alarmid)
         model.delete_alarm(alarmid)
     except KeyError:
     except KeyError:
         abort(404)
         abort(404)
     if request_wants_json():
     if request_wants_json():
         return jsonify(dict(deleted=alarmid))
         return jsonify(dict(deleted=alarmid))
-    flash('Evento %d `%s` cancellato' % (alarmid, alarm['nick']) )
-    return redirect(url_for('db.events_list'))
+    flash("Evento %d `%s` cancellato" % (alarmid, alarm["nick"]))
+    return redirect(url_for("db.events_list"))

+ 17 - 19
larigira/dbadmin/suggestions.py

@@ -7,22 +7,23 @@ from larigira.fsutils import scan_dir_audio
 
 
 
 
 def get_suggested_files():
 def get_suggested_files():
-    if not get_conf()['FILE_PATH_SUGGESTION']:
+    if not get_conf()["FILE_PATH_SUGGESTION"]:
         return []
         return []
-    if current_app.cache.has('dbadmin.get_suggested_files'):
-        return current_app.cache.get('dbadmin.get_suggested_files')
-    current_app.logger.debug('get_suggested_files MISS in cache')
+    if current_app.cache.has("dbadmin.get_suggested_files"):
+        return current_app.cache.get("dbadmin.get_suggested_files")
+    current_app.logger.debug("get_suggested_files MISS in cache")
     files = []
     files = []
-    for path in get_conf()['FILE_PATH_SUGGESTION']:
+    for path in get_conf()["FILE_PATH_SUGGESTION"]:
         if not os.path.isdir(path):
         if not os.path.isdir(path):
-            current_app.logger.warning('Invalid suggestion path: %s' % path)
+            current_app.logger.warning("Invalid suggestion path: %s" % path)
             continue
             continue
         pathfiles = scan_dir_audio(path)
         pathfiles = scan_dir_audio(path)
         files.extend(pathfiles)
         files.extend(pathfiles)
-    current_app.logger.debug('Suggested files: %s' % ', '.join(files))
+    current_app.logger.debug("Suggested files: %s" % ", ".join(files))
 
 
-    current_app.cache.set('dbadmin.get_suggested_files', files,
-                          timeout=600)  # ten minutes
+    current_app.cache.set(
+        "dbadmin.get_suggested_files", files, timeout=600
+    )  # ten minutes
     return files
     return files
 
 
 
 
@@ -40,11 +41,14 @@ def get_suggested_dirs():
 
 
 
 
 def get_suggested_scripts():
 def get_suggested_scripts():
-    base = get_conf()['SCRIPTS_PATH']
+    base = get_conf()["SCRIPTS_PATH"]
     if not base or not os.path.isdir(base):
     if not base or not os.path.isdir(base):
         return []
         return []
-    fnames = [f for f in os.listdir(base)
-              if os.access(os.path.join(base, f), os.R_OK | os.X_OK)]
+    fnames = [
+        f
+        for f in os.listdir(base)
+        if os.access(os.path.join(base, f), os.R_OK | os.X_OK)
+    ]
     return fnames
     return fnames
 
 
 
 
@@ -53,10 +57,4 @@ def get_suggestions():
     if len(files) > 200:
     if len(files) > 200:
         current_app.logger.warning("Too many suggested files, cropping")
         current_app.logger.warning("Too many suggested files, cropping")
         files = files[:200]
         files = files[:200]
-    return dict(
-        files=files,
-        dirs=get_suggested_dirs(),
-        scripts=get_suggested_scripts(),
-    )
-
-
+    return dict(files=files, dirs=get_suggested_dirs(), scripts=get_suggested_scripts())

+ 4 - 3
larigira/entrypoints_utils.py

@@ -1,13 +1,14 @@
 from logging import getLogger
 from logging import getLogger
-log = getLogger('entrypoints_utils')
+
+log = getLogger("entrypoints_utils")
 from pkg_resources import iter_entry_points
 from pkg_resources import iter_entry_points
 
 
 
 
 def get_one_entrypoint(group, kind):
 def get_one_entrypoint(group, kind):
-    '''Messes with entrypoints to return an entrypoint of a given group/kind'''
+    """Messes with entrypoints to return an entrypoint of a given group/kind"""
     points = tuple(iter_entry_points(group=group, name=kind))
     points = tuple(iter_entry_points(group=group, name=kind))
     if not points:
     if not points:
-        raise ValueError('cant find an entrypoint %s:%s' % (group, kind))
+        raise ValueError("cant find an entrypoint %s:%s" % (group, kind))
     if len(points) > 1:
     if len(points) > 1:
         log.warning("Found more than one timeform for %s:%s", group, kind)
         log.warning("Found more than one timeform for %s:%s", group, kind)
     return points[0].load()
     return points[0].load()

+ 59 - 46
larigira/event.py

@@ -1,5 +1,6 @@
 from __future__ import print_function
 from __future__ import print_function
 from gevent import monkey
 from gevent import monkey
+
 monkey.patch_all(subprocess=True)
 monkey.patch_all(subprocess=True)
 import logging
 import logging
 from datetime import datetime, timedelta
 from datetime import datetime, timedelta
@@ -12,10 +13,11 @@ from .timegen import timegenerate
 from .audiogen import audiogenerate
 from .audiogen import audiogenerate
 from .db import EventModel
 from .db import EventModel
 
 
-logging.getLogger('mpd').setLevel(logging.WARNING)
+logging.getLogger("mpd").setLevel(logging.WARNING)
+
 
 
 class Monitor(ParentedLet):
 class Monitor(ParentedLet):
-    '''
+    """
     Manages timegenerators and audiogenerators for DB events
     Manages timegenerators and audiogenerators for DB events
     
     
     The mechanism is partially based on ticks, partially on scheduled actions.
     The mechanism is partially based on ticks, partially on scheduled actions.
@@ -30,23 +32,25 @@ class Monitor(ParentedLet):
     The scheduling mechanism allows for more precision, catching exactly the
     The scheduling mechanism allows for more precision, catching exactly the
     right time. Being accurate only with ticks would have required very
     right time. Being accurate only with ticks would have required very
     frequent ticks, which is cpu-intensive.
     frequent ticks, which is cpu-intensive.
-    '''
+    """
+
     def __init__(self, parent_queue, conf):
     def __init__(self, parent_queue, conf):
         ParentedLet.__init__(self, parent_queue)
         ParentedLet.__init__(self, parent_queue)
         self.log = logging.getLogger(self.__class__.__name__)
         self.log = logging.getLogger(self.__class__.__name__)
         self.running = {}
         self.running = {}
         self.conf = conf
         self.conf = conf
         self.q = Queue()
         self.q = Queue()
-        self.model = EventModel(self.conf['DB_URI'])
-        self.ticker = Timer(int(self.conf['EVENT_TICK_SECS']) * 1000, self.q)
+        self.model = EventModel(self.conf["DB_URI"])
+        self.ticker = Timer(int(self.conf["EVENT_TICK_SECS"]) * 1000, self.q)
 
 
     def _alarm_missing_time(self, timespec):
     def _alarm_missing_time(self, timespec):
-        now = datetime.now() + timedelta(seconds=self.conf['CACHING_TIME'])
+        now = datetime.now() + timedelta(seconds=self.conf["CACHING_TIME"])
         try:
         try:
             when = next(timegenerate(timespec, now=now))
             when = next(timegenerate(timespec, now=now))
         except:
         except:
-            logging.exception("Could not generate "
-                              "an alarm from timespec %s", timespec)
+            logging.exception(
+                "Could not generate " "an alarm from timespec %s", timespec
+            )
         if when is None:
         if when is None:
             # expired
             # expired
             return None
             return None
@@ -55,12 +59,12 @@ class Monitor(ParentedLet):
         return delta
         return delta
 
 
     def on_tick(self):
     def on_tick(self):
-        '''
+        """
         this is called every EVENT_TICK_SECS.
         this is called every EVENT_TICK_SECS.
         Checks every event in the DB (which might be slightly CPU-intensive, so
         Checks every event in the DB (which might be slightly CPU-intensive, so
         it is advisable to run it in its own greenlet); if the event is "near
         it is advisable to run it in its own greenlet); if the event is "near
         enough", schedule it; if it is too far, or already expired, ignore it.
         enough", schedule it; if it is too far, or already expired, ignore it.
-        '''
+        """
         self.model.reload()
         self.model.reload()
         for alarm in self.model.get_all_alarms():
         for alarm in self.model.get_all_alarms():
             actions = list(self.model.get_actions_by_alarm(alarm))
             actions = list(self.model.get_actions_by_alarm(alarm))
@@ -71,75 +75,84 @@ class Monitor(ParentedLet):
             # but it is "tricky"; any small delay would cause the event to be
             # but it is "tricky"; any small delay would cause the event to be
             # missed
             # missed
             if delta is None:
             if delta is None:
-                self.log.debug('Skipping event %s: will never ring',
-                               alarm.get('nick', alarm.eid))
-            elif delta <= 2*self.conf['EVENT_TICK_SECS']:
-                self.log.debug('Scheduling event %s (%ds) => %s',
-                               alarm.get('nick', alarm.eid),
-                               delta,
-                               [a.get('nick', a.eid) for a in actions]
-                               )
+                self.log.debug(
+                    "Skipping event %s: will never ring", alarm.get("nick", alarm.eid)
+                )
+            elif delta <= 2 * self.conf["EVENT_TICK_SECS"]:
+                self.log.debug(
+                    "Scheduling event %s (%ds) => %s",
+                    alarm.get("nick", alarm.eid),
+                    delta,
+                    [a.get("nick", a.eid) for a in actions],
+                )
                 self.schedule(alarm, actions, delta)
                 self.schedule(alarm, actions, delta)
             else:
             else:
-                self.log.debug('Skipping event %s too far (%ds)',
-                               alarm.get('nick', alarm.eid),
-                               delta,
-                               )
+                self.log.debug(
+                    "Skipping event %s too far (%ds)",
+                    alarm.get("nick", alarm.eid),
+                    delta,
+                )
 
 
     def schedule(self, timespec, audiospecs, delta=None):
     def schedule(self, timespec, audiospecs, delta=None):
-        '''
+        """
         prepare an event to be run at a specified time with the specified
         prepare an event to be run at a specified time with the specified
         actions; the DB won't be read anymore after this call.
         actions; the DB won't be read anymore after this call.
 
 
         This means that this call should not be done too early, or any update
         This means that this call should not be done too early, or any update
         to the DB will be ignored.
         to the DB will be ignored.
-        '''
+        """
         if delta is None:
         if delta is None:
             delta = self._alarm_missing_time(timespec)
             delta = self._alarm_missing_time(timespec)
 
 
-        audiogen = gevent.spawn_later(delta,
-                                      self.process_action,
-                                      timespec, audiospecs)
+        audiogen = gevent.spawn_later(delta, self.process_action, timespec, audiospecs)
         audiogen.parent_greenlet = self
         audiogen.parent_greenlet = self
         audiogen.doc = 'Will wait {} seconds, then generate audio "{}"'.format(
         audiogen.doc = 'Will wait {} seconds, then generate audio "{}"'.format(
-            delta,
-            ','.join(aspec.get('nick', '') for aspec in audiospecs),
+            delta, ",".join(aspec.get("nick", "") for aspec in audiospecs)
         )
         )
         self.running[timespec.eid] = {
         self.running[timespec.eid] = {
-            'greenlet': audiogen,
-            'running_time': datetime.now() + timedelta(seconds=delta),
-            'timespec': timespec,
-            'audiospecs': audiospecs
+            "greenlet": audiogen,
+            "running_time": datetime.now() + timedelta(seconds=delta),
+            "timespec": timespec,
+            "audiospecs": audiospecs,
         }
         }
 
 
     def process_action(self, timespec, audiospecs):
     def process_action(self, timespec, audiospecs):
-        '''Generate audio and submit it to Controller'''
+        """Generate audio and submit it to Controller"""
         if timespec.eid in self.running:
         if timespec.eid in self.running:
             del self.running[timespec.eid]
             del self.running[timespec.eid]
         else:
         else:
-            self.log.warning('Timespec %s completed but not in running '
-                             'registry; this is most likely a bug',
-                             timespec.get('nick', timespec.eid))
+            self.log.warning(
+                "Timespec %s completed but not in running "
+                "registry; this is most likely a bug",
+                timespec.get("nick", timespec.eid),
+            )
         uris = []
         uris = []
         for audiospec in audiospecs:
         for audiospec in audiospecs:
             try:
             try:
                 uris.extend(audiogenerate(audiospec))
                 uris.extend(audiogenerate(audiospec))
             except Exception as exc:
             except Exception as exc:
-                self.log.error('audiogenerate for <%s> failed; reason: %s',
-                               str(audiospec), str(exc))
-        self.send_to_parent('uris_enqueue',
-                            dict(uris=uris,
-                                 timespec=timespec,
-                                 audiospecs=audiospecs,
-                                 aids=[a.eid for a in audiospecs]))
+                self.log.error(
+                    "audiogenerate for <%s> failed; reason: %s",
+                    str(audiospec),
+                    str(exc),
+                )
+        self.send_to_parent(
+            "uris_enqueue",
+            dict(
+                uris=uris,
+                timespec=timespec,
+                audiospecs=audiospecs,
+                aids=[a.eid for a in audiospecs],
+            ),
+        )
 
 
     def _run(self):
     def _run(self):
         self.ticker.start()
         self.ticker.start()
         gevent.spawn(self.on_tick)
         gevent.spawn(self.on_tick)
         while True:
         while True:
             value = self.q.get()
             value = self.q.get()
-            kind = value['kind']
-            if kind in ('forcetick', 'timer'):
+            kind = value["kind"]
+            if kind in ("forcetick", "timer"):
                 gevent.spawn(self.on_tick)
                 gevent.spawn(self.on_tick)
             else:
             else:
                 self.log.warning("Unknown message: %s", str(value))
                 self.log.warning("Unknown message: %s", str(value))

+ 12 - 9
larigira/event_manage.py

@@ -14,25 +14,28 @@ def main_list(args):
 
 
 def main_add(args):
 def main_add(args):
     m = EventModel(args.file)
     m = EventModel(args.file)
-    m.add_event(dict(kind='frequency', interval=args.interval, start=1),
-                [dict(kind='mpd', howmany=1)]
-                )
+    m.add_event(
+        dict(kind="frequency", interval=args.interval, start=1),
+        [dict(kind="mpd", howmany=1)],
+    )
 
 
 
 
 def main():
 def main():
     conf = get_conf()
     conf = get_conf()
     p = argparse.ArgumentParser()
     p = argparse.ArgumentParser()
-    p.add_argument('-f', '--file', help="Filepath for DB", required=False,
-                   default=conf['DB_URI'])
+    p.add_argument(
+        "-f", "--file", help="Filepath for DB", required=False, default=conf["DB_URI"]
+    )
     sub = p.add_subparsers()
     sub = p.add_subparsers()
-    sub_list = sub.add_parser('list')
+    sub_list = sub.add_parser("list")
     sub_list.set_defaults(func=main_list)
     sub_list.set_defaults(func=main_list)
-    sub_add = sub.add_parser('add')
-    sub_add.add_argument('--interval', type=int, default=3600)
+    sub_add = sub.add_parser("add")
+    sub_add.add_argument("--interval", type=int, default=3600)
     sub_add.set_defaults(func=main_add)
     sub_add.set_defaults(func=main_add)
 
 
     args = p.parse_args()
     args = p.parse_args()
     args.func(args)
     args.func(args)
 
 
-if __name__ == '__main__':
+
+if __name__ == "__main__":
     main()
     main()

+ 16 - 13
larigira/eventutils.py

@@ -2,13 +2,14 @@ import gevent
 
 
 
 
 class ParentedLet(gevent.Greenlet):
 class ParentedLet(gevent.Greenlet):
-    '''
+    """
     ParentedLet is just a helper subclass that will help you when your
     ParentedLet is just a helper subclass that will help you when your
     greenlet main duty is to "signal" things to a parent_queue.
     greenlet main duty is to "signal" things to a parent_queue.
 
 
     It won't save you much code, but "standardize" messages and make explicit
     It won't save you much code, but "standardize" messages and make explicit
     the role of that greenlet
     the role of that greenlet
-    '''
+    """
+
     def __init__(self, queue):
     def __init__(self, queue):
         gevent.Greenlet.__init__(self)
         gevent.Greenlet.__init__(self)
         self.parent_queue = queue
         self.parent_queue = queue
@@ -17,36 +18,38 @@ class ParentedLet(gevent.Greenlet):
 
 
     def parent_msg(self, kind, *args):
     def parent_msg(self, kind, *args):
         return {
         return {
-            'emitter': self,
-            'class': self.__class__.__name__,
-            'tracker': self.tracker,
-            'kind': kind,
-            'args': args
+            "emitter": self,
+            "class": self.__class__.__name__,
+            "tracker": self.tracker,
+            "kind": kind,
+            "args": args,
         }
         }
 
 
     def send_to_parent(self, kind, *args):
     def send_to_parent(self, kind, *args):
         self.parent_queue.put(self.parent_msg(kind, *args))
         self.parent_queue.put(self.parent_msg(kind, *args))
 
 
     def _run(self):
     def _run(self):
-        if not hasattr(self, 'do_business'):
-            raise Exception("do_business method not implemented by %s" %
-                            self.__class__.__name__)
+        if not hasattr(self, "do_business"):
+            raise Exception(
+                "do_business method not implemented by %s" % self.__class__.__name__
+            )
         for msg in self.do_business():
         for msg in self.do_business():
             self.send_to_parent(*msg)
             self.send_to_parent(*msg)
 
 
 
 
 class Timer(ParentedLet):
 class Timer(ParentedLet):
-    '''continously sleeps some time, then send a "timer" message to parent'''
+    """continously sleeps some time, then send a "timer" message to parent"""
+
     def __init__(self, milliseconds, queue):
     def __init__(self, milliseconds, queue):
         ParentedLet.__init__(self, queue)
         ParentedLet.__init__(self, queue)
         self.ms = milliseconds
         self.ms = milliseconds
 
 
     def parent_msg(self, kind, *args):
     def parent_msg(self, kind, *args):
         msg = ParentedLet.parent_msg(self, kind, *args)
         msg = ParentedLet.parent_msg(self, kind, *args)
-        msg['period'] = self.ms
+        msg["period"] = self.ms
         return msg
         return msg
 
 
     def do_business(self):
     def do_business(self):
         while True:
         while True:
             gevent.sleep(self.ms / 1000.0)
             gevent.sleep(self.ms / 1000.0)
-            yield ('timer', )
+            yield ("timer",)

+ 20 - 20
larigira/filters/basic.py

@@ -2,22 +2,22 @@ import wave
 
 
 
 
 def maxwait(songs, context, conf):
 def maxwait(songs, context, conf):
-    wait = int(conf.get('EF_MAXWAIT_SEC', 0))
+    wait = int(conf.get("EF_MAXWAIT_SEC", 0))
     if wait == 0:
     if wait == 0:
         return True
         return True
-    if 'time' not in context['status']:
-        return True, 'no song playing?'
-    curpos, duration = map(int, context['status']['time'].split(':'))
+    if "time" not in context["status"]:
+        return True, "no song playing?"
+    curpos, duration = map(int, context["status"]["time"].split(":"))
     remaining = duration - curpos
     remaining = duration - curpos
     if remaining > wait:
     if remaining > wait:
-        return False, 'remaining %d max allowed %d' % (remaining, wait)
+        return False, "remaining %d max allowed %d" % (remaining, wait)
     return True
     return True
 
 
 
 
 def get_duration(path):
 def get_duration(path):
-    '''get track duration in seconds'''
-    if path.lower().endswith('.wav'):
-        with wave.open(path, 'r') as f:
+    """get track duration in seconds"""
+    if path.lower().endswith(".wav"):
+        with wave.open(path, "r") as f:
             frames = f.getnframes()
             frames = f.getnframes()
             rate = f.getframerate()
             rate = f.getframerate()
             duration = frames / rate
             duration = frames / rate
@@ -34,7 +34,7 @@ def get_duration(path):
 
 
 
 
 def percentwait(songs, context, conf, getdur=get_duration):
 def percentwait(songs, context, conf, getdur=get_duration):
-    '''
+    """
     Similar to maxwait, but the maximum waiting time is proportional to the
     Similar to maxwait, but the maximum waiting time is proportional to the
     duration of the audio we're going to add.
     duration of the audio we're going to add.
 
 
@@ -46,25 +46,25 @@ def percentwait(songs, context, conf, getdur=get_duration):
     are adding a jingle of 40seconds, then if EF_MAXWAIT_PERC==200 the audio
     are adding a jingle of 40seconds, then if EF_MAXWAIT_PERC==200 the audio
     will be added (40s*200% = 1m20s) while if EF_MAXWAIT_PERC==100 it will be
     will be added (40s*200% = 1m20s) while if EF_MAXWAIT_PERC==100 it will be
     filtered out.
     filtered out.
-    '''
-    percentwait = int(conf.get('EF_MAXWAIT_PERC', 0))
+    """
+    percentwait = int(conf.get("EF_MAXWAIT_PERC", 0))
     if percentwait == 0:
     if percentwait == 0:
         return True
         return True
-    if 'time' not in context['status']:
-        return True, 'no song playing?'
-    curpos, duration = map(int, context['status']['time'].split(':'))
+    if "time" not in context["status"]:
+        return True, "no song playing?"
+    curpos, duration = map(int, context["status"]["time"].split(":"))
     remaining = duration - curpos
     remaining = duration - curpos
     eventduration = 0
     eventduration = 0
-    for uri in songs['uris']:
-        if not uri.startswith('file://'):
-            return True, '%s is not a file' % uri
-        path = uri[len('file://'):]  # strips file://
+    for uri in songs["uris"]:
+        if not uri.startswith("file://"):
+            return True, "%s is not a file" % uri
+        path = uri[len("file://") :]  # strips file://
         songduration = getdur(path)
         songduration = getdur(path)
         if songduration is None:
         if songduration is None:
             continue
             continue
         eventduration += songduration
         eventduration += songduration
 
 
-    wait = eventduration * (percentwait/100.)
+    wait = eventduration * (percentwait / 100.0)
     if remaining > wait:
     if remaining > wait:
-        return False, 'remaining %d max allowed %d' % (remaining, wait)
+        return False, "remaining %d max allowed %d" % (remaining, wait)
     return True
     return True

+ 40 - 23
larigira/filters/tests/test_basic.py

@@ -9,17 +9,26 @@ def matchval(d):
             if k in input_:  # string matching
             if k in input_:  # string matching
                 return d[k]
                 return d[k]
         raise Exception("This test case is bugged! No value for %s" % input_)
         raise Exception("This test case is bugged! No value for %s" % input_)
+
     return mocked
     return mocked
 
 
 
 
-durations = dict(one=60, two=120, three=180, four=240, ten=600, twenty=1200,
-                 thirty=1800, nonexist=None)
+durations = dict(
+    one=60,
+    two=120,
+    three=180,
+    four=240,
+    ten=600,
+    twenty=1200,
+    thirty=1800,
+    nonexist=None,
+)
 dur = matchval(durations)
 dur = matchval(durations)
 
 
 
 
 def normalize_ret(ret):
 def normalize_ret(ret):
     if type(ret) is bool:
     if type(ret) is bool:
-        return ret, ''
+        return ret, ""
     return ret
     return ret
 
 
 
 
@@ -28,7 +37,7 @@ def mw(*args, **kwargs):
 
 
 
 
 def pw(*args, **kwargs):
 def pw(*args, **kwargs):
-    kwargs['getdur'] = dur
+    kwargs["getdur"] = dur
     return normalize_ret(percentwait(*args, **kwargs))
     return normalize_ret(percentwait(*args, **kwargs))
 
 
 
 
@@ -38,26 +47,26 @@ def test_maxwait_nonpresent_disabled():
 
 
 
 
 def test_maxwait_explicitly_disabled():
 def test_maxwait_explicitly_disabled():
-    ret = mw([], {}, {'EF_MAXWAIT_SEC': 0})
+    ret = mw([], {}, {"EF_MAXWAIT_SEC": 0})
     assert ret[0] is True
     assert ret[0] is True
 
 
 
 
 def test_maxwait_ok():
 def test_maxwait_ok():
-    ret = mw([], {'status': {'time': '250:300'}}, {'EF_MAXWAIT_SEC': 100})
+    ret = mw([], {"status": {"time": "250:300"}}, {"EF_MAXWAIT_SEC": 100})
     assert ret[0] is True
     assert ret[0] is True
 
 
 
 
 def test_maxwait_exceeded():
 def test_maxwait_exceeded():
-    ret = mw([], {'status': {'time': '100:300'}}, {'EF_MAXWAIT_SEC': 100})
+    ret = mw([], {"status": {"time": "100:300"}}, {"EF_MAXWAIT_SEC": 100})
     assert ret[0] is False
     assert ret[0] is False
 
 
 
 
 def test_maxwait_limit():
 def test_maxwait_limit():
-    ret = mw([], {'status': {'time': '199:300'}}, {'EF_MAXWAIT_SEC': 100})
+    ret = mw([], {"status": {"time": "199:300"}}, {"EF_MAXWAIT_SEC": 100})
     assert ret[0] is False
     assert ret[0] is False
-    ret = mw([], {'status': {'time': '200:300'}}, {'EF_MAXWAIT_SEC': 100})
+    ret = mw([], {"status": {"time": "200:300"}}, {"EF_MAXWAIT_SEC": 100})
     assert ret[0] is True
     assert ret[0] is True
-    ret = mw([], {'status': {'time': '201:300'}}, {'EF_MAXWAIT_SEC': 100})
+    ret = mw([], {"status": {"time": "201:300"}}, {"EF_MAXWAIT_SEC": 100})
     assert ret[0] is True
     assert ret[0] is True
 
 
 
 
@@ -67,32 +76,40 @@ def test_percentwait_nonpresent_disabled():
 
 
 
 
 def test_percentwait_explicitly_disabled():
 def test_percentwait_explicitly_disabled():
-    ret = pw([], {}, {'EF_MAXWAIT_PERC': 0})
+    ret = pw([], {}, {"EF_MAXWAIT_PERC": 0})
     assert ret[0] is True
     assert ret[0] is True
 
 
 
 
 def test_percentwait_ok():
 def test_percentwait_ok():
     # less than one minute missing
     # less than one minute missing
-    ret = pw(dict(uris=['file:///oneminute.ogg']),
-             {'status': {'time': '250:300'}},
-             {'EF_MAXWAIT_PERC': 100})
+    ret = pw(
+        dict(uris=["file:///oneminute.ogg"]),
+        {"status": {"time": "250:300"}},
+        {"EF_MAXWAIT_PERC": 100},
+    )
     assert ret[0] is True
     assert ret[0] is True
 
 
     # more than one minute missing
     # more than one minute missing
-    ret = pw(dict(uris=['file:///oneminute.ogg']),
-             {'status': {'time': '220:300'}},
-             {'EF_MAXWAIT_PERC': 100})
+    ret = pw(
+        dict(uris=["file:///oneminute.ogg"]),
+        {"status": {"time": "220:300"}},
+        {"EF_MAXWAIT_PERC": 100},
+    )
     assert ret[0] is False
     assert ret[0] is False
 
 
 
 
 def test_percentwait_morethan100():
 def test_percentwait_morethan100():
     # requiring 5*10 = 50mins = 3000sec
     # requiring 5*10 = 50mins = 3000sec
-    ret = pw(dict(uris=['file:///tenminute.ogg']),
-             {'status': {'time': '4800:6000'}},
-             {'EF_MAXWAIT_PERC': 500})
+    ret = pw(
+        dict(uris=["file:///tenminute.ogg"]),
+        {"status": {"time": "4800:6000"}},
+        {"EF_MAXWAIT_PERC": 500},
+    )
     assert ret[0] is True
     assert ret[0] is True
 
 
-    ret = pw(dict(uris=['file:///oneminute.ogg']),
-             {'status': {'time': '2000:6000'}},
-             {'EF_MAXWAIT_PERC': 500})
+    ret = pw(
+        dict(uris=["file:///oneminute.ogg"]),
+        {"status": {"time": "2000:6000"}},
+        {"EF_MAXWAIT_PERC": 500},
+    )
     assert ret[0] is False
     assert ret[0] is False

+ 6 - 5
larigira/forms.py

@@ -1,15 +1,16 @@
 from logging import getLogger
 from logging import getLogger
-log = getLogger('timeform')
+
+log = getLogger("timeform")
 from .entrypoints_utils import get_one_entrypoint
 from .entrypoints_utils import get_one_entrypoint
 
 
 
 
 def get_timeform(kind):
 def get_timeform(kind):
-    '''Messes with entrypoints to return a TimeForm'''
-    for group in ('larigira.timeform_create', 'larigira.timeform_receive'):
+    """Messes with entrypoints to return a TimeForm"""
+    for group in ("larigira.timeform_create", "larigira.timeform_receive"):
         yield get_one_entrypoint(group, kind)
         yield get_one_entrypoint(group, kind)
 
 
 
 
 def get_audioform(kind):
 def get_audioform(kind):
-    '''Messes with entrypoints to return a AudioForm'''
-    for group in ('larigira.audioform_create', 'larigira.audioform_receive'):
+    """Messes with entrypoints to return a AudioForm"""
+    for group in ("larigira.audioform_create", "larigira.audioform_receive"):
         yield get_one_entrypoint(group, kind)
         yield get_one_entrypoint(group, kind)

+ 17 - 17
larigira/formutils.py

@@ -11,17 +11,16 @@ log = logging.getLogger(__name__)
 
 
 class AutocompleteTextInput(wtforms.widgets.Input):
 class AutocompleteTextInput(wtforms.widgets.Input):
     def __init__(self, datalist=None):
     def __init__(self, datalist=None):
-        super().__init__('text')
+        super().__init__("text")
         self.datalist = datalist
         self.datalist = datalist
 
 
     def __call__(self, field, **kwargs):
     def __call__(self, field, **kwargs):
         # every second can be specified
         # every second can be specified
         if self.datalist is not None:
         if self.datalist is not None:
             return super(AutocompleteTextInput, self).__call__(
             return super(AutocompleteTextInput, self).__call__(
-                field, list=self.datalist, autocomplete="autocomplete",
-                **kwargs)
-        return super(AutocompleteTextInput, self).__call__(
-            field, **kwargs)
+                field, list=self.datalist, autocomplete="autocomplete", **kwargs
+            )
+        return super(AutocompleteTextInput, self).__call__(field, **kwargs)
 
 
 
 
 class AutocompleteStringField(StringField):
 class AutocompleteStringField(StringField):
@@ -31,15 +30,15 @@ class AutocompleteStringField(StringField):
 
 
 
 
 class DateTimeInput(wtforms.widgets.Input):
 class DateTimeInput(wtforms.widgets.Input):
-    input_type = 'datetime-local'
+    input_type = "datetime-local"
 
 
     def __call__(self, field, **kwargs):
     def __call__(self, field, **kwargs):
         # every second can be specified
         # every second can be specified
-        return super(DateTimeInput, self).__call__(field, step='1', **kwargs)
+        return super(DateTimeInput, self).__call__(field, step="1", **kwargs)
 
 
 
 
 class EasyDateTimeField(Field):
 class EasyDateTimeField(Field):
-    '''
+    """
     a "fork" of DateTimeField which uses HTML5 datetime-local
     a "fork" of DateTimeField which uses HTML5 datetime-local
 
 
     The format is not customizable, because it is imposed by the HTML5
     The format is not customizable, because it is imposed by the HTML5
@@ -47,27 +46,28 @@ class EasyDateTimeField(Field):
 
 
     This field does not ensure that browser actually supports datetime-local
     This field does not ensure that browser actually supports datetime-local
     input type, nor does it provide polyfills.
     input type, nor does it provide polyfills.
-    '''
+    """
+
     widget = DateTimeInput()
     widget = DateTimeInput()
-    formats = ('%Y-%m-%dT%H:%M:%S', '%Y-%m-%dT%H:%M')
+    formats = ("%Y-%m-%dT%H:%M:%S", "%Y-%m-%dT%H:%M")
 
 
     def __init__(self, label=None, validators=None, **kwargs):
     def __init__(self, label=None, validators=None, **kwargs):
         super(EasyDateTimeField, self).__init__(label, validators, **kwargs)
         super(EasyDateTimeField, self).__init__(label, validators, **kwargs)
 
 
     def _value(self):
     def _value(self):
         if self.raw_data:
         if self.raw_data:
-            return ' '.join(self.raw_data)
-        return self.data and self.data.strftime(self.formats[0]) or ''
+            return " ".join(self.raw_data)
+        return self.data and self.data.strftime(self.formats[0]) or ""
 
 
     def process_formdata(self, valuelist):
     def process_formdata(self, valuelist):
         if valuelist:
         if valuelist:
-            date_str = ' '.join(valuelist)
+            date_str = " ".join(valuelist)
             for fmt in self.formats:
             for fmt in self.formats:
                 try:
                 try:
                     self.data = datetime.strptime(date_str, fmt)
                     self.data = datetime.strptime(date_str, fmt)
                     return
                     return
                 except ValueError:
                 except ValueError:
-                    log.debug('Format `%s` not valid for `%s`',
-                              fmt, date_str)
-            raise ValueError(self.gettext(
-                'Not a valid datetime value <tt>{}</tt>').format(date_str))
+                    log.debug("Format `%s` not valid for `%s`", fmt, date_str)
+            raise ValueError(
+                self.gettext("Not a valid datetime value <tt>{}</tt>").format(date_str)
+            )

+ 6 - 6
larigira/fsutils.py

@@ -5,7 +5,7 @@ import mimetypes
 
 
 def scan_dir(dirname, extension=None):
 def scan_dir(dirname, extension=None):
     if extension is None:
     if extension is None:
-        extension = '*'
+        extension = "*"
     for root, dirnames, filenames in os.walk(dirname):
     for root, dirnames, filenames in os.walk(dirname):
         for fname in fnmatch.filter(filenames, extension):
         for fname in fnmatch.filter(filenames, extension):
             yield os.path.join(root, fname)
             yield os.path.join(root, fname)
@@ -13,7 +13,7 @@ def scan_dir(dirname, extension=None):
 
 
 def multi_fnmatch(fname, extensions):
 def multi_fnmatch(fname, extensions):
     for ext in extensions:
     for ext in extensions:
-        if fnmatch.fnmatch(fname, '*.' + ext):
+        if fnmatch.fnmatch(fname, "*." + ext):
             return True
             return True
     return False
     return False
 
 
@@ -22,10 +22,10 @@ def is_audio(fname):
     mimetype = mimetypes.guess_type(fname)[0]
     mimetype = mimetypes.guess_type(fname)[0]
     if mimetype is None:
     if mimetype is None:
         return False
         return False
-    return mimetype.split('/')[0] == 'audio'
+    return mimetype.split("/")[0] == "audio"
 
 
 
 
-def scan_dir_audio(dirname, extensions=('mp3', 'oga', 'wav', 'ogg')):
+def scan_dir_audio(dirname, extensions=("mp3", "oga", "wav", "ogg")):
     for root, dirnames, filenames in os.walk(dirname):
     for root, dirnames, filenames in os.walk(dirname):
         for fname in filenames:
         for fname in filenames:
             if is_audio(fname):
             if is_audio(fname):
@@ -34,6 +34,6 @@ def scan_dir_audio(dirname, extensions=('mp3', 'oga', 'wav', 'ogg')):
 
 
 def shortname(path):
 def shortname(path):
     name = os.path.basename(path)  # filename
     name = os.path.basename(path)  # filename
-    name = name.rsplit('.', 1)[0]   # no extension
-    name = ''.join(c for c in name if c.isalnum())  # no strange chars
+    name = name.rsplit(".", 1)[0]  # no extension
+    name = "".join(c for c in name if c.isalnum())  # no strange chars
     return name
     return name

+ 79 - 71
larigira/mpc.py

@@ -16,15 +16,16 @@ from .entrypoints_utils import get_avail_entrypoints
 
 
 def get_mpd_client(conf):
 def get_mpd_client(conf):
     client = mpd.MPDClient(use_unicode=True)
     client = mpd.MPDClient(use_unicode=True)
-    client.connect(conf['MPD_HOST'], conf['MPD_PORT'])
+    client.connect(conf["MPD_HOST"], conf["MPD_PORT"])
 
 
     return client
     return client
 
 
 
 
 class MPDWatcher(ParentedLet):
 class MPDWatcher(ParentedLet):
-    '''
+    """
     MPDWatcher notifies parent about any mpd event
     MPDWatcher notifies parent about any mpd event
-    '''
+    """
+
     def __init__(self, queue, conf, client=None):
     def __init__(self, queue, conf, client=None):
         ParentedLet.__init__(self, queue)
         ParentedLet.__init__(self, queue)
         self.log = logging.getLogger(self.__class__.__name__)
         self.log = logging.getLogger(self.__class__.__name__)
@@ -41,46 +42,51 @@ class MPDWatcher(ParentedLet):
                 if self.client is None:
                 if self.client is None:
                     self.refresh_client()
                     self.refresh_client()
                 if first_after_connection:
                 if first_after_connection:
-                    yield('mpc', 'connect')
+                    yield ("mpc", "connect")
 
 
                 status = self.client.idle()[0]
                 status = self.client.idle()[0]
-            except (mpd.ConnectionError, ConnectionRefusedError,
-                    FileNotFoundError) as exc:
-                self.log.warning('Connection to MPD failed (%s: %s)',
-                                 exc.__class__.__name__, exc)
+            except (
+                mpd.ConnectionError,
+                ConnectionRefusedError,
+                FileNotFoundError,
+            ) as exc:
+                self.log.warning(
+                    "Connection to MPD failed (%s: %s)", exc.__class__.__name__, exc
+                )
                 self.client = None
                 self.client = None
                 first_after_connection = True
                 first_after_connection = True
                 gevent.sleep(5)
                 gevent.sleep(5)
                 continue
                 continue
             else:
             else:
                 first_after_connection = False
                 first_after_connection = False
-                yield ('mpc', status)
+                yield ("mpc", status)
 
 
 
 
 class Player:
 class Player:
-    '''
+    """
     The player contains different mpd-related methods
     The player contains different mpd-related methods
 
 
     check_playlist determines whether the playlist is long enough and run audiogenerator accordingly
     check_playlist determines whether the playlist is long enough and run audiogenerator accordingly
 
 
     enqueue receive audios that have been generated by Monitor and (if filters allow it) enqueue it to MPD playlist
     enqueue receive audios that have been generated by Monitor and (if filters allow it) enqueue it to MPD playlist
-    '''
+    """
+
     def __init__(self, conf):
     def __init__(self, conf):
         self.conf = conf
         self.conf = conf
         self.log = logging.getLogger(self.__class__.__name__)
         self.log = logging.getLogger(self.__class__.__name__)
         self.min_playlist_length = 10
         self.min_playlist_length = 10
         self.tmpcleaner = UnusedCleaner(conf)
         self.tmpcleaner = UnusedCleaner(conf)
-        self._continous_audiospec = self.conf['CONTINOUS_AUDIOSPEC']
+        self._continous_audiospec = self.conf["CONTINOUS_AUDIOSPEC"]
         self.events_enabled = True
         self.events_enabled = True
 
 
     def _get_mpd(self):
     def _get_mpd(self):
         mpd_client = mpd.MPDClient(use_unicode=True)
         mpd_client = mpd.MPDClient(use_unicode=True)
         try:
         try:
-            mpd_client.connect(self.conf['MPD_HOST'], self.conf['MPD_PORT'])
-        except (mpd.ConnectionError, ConnectionRefusedError,
-                FileNotFoundError) as exc:
-            self.log.warning('Connection to MPD failed (%s: %s)',
-                             exc.__class__.__name__, exc)
+            mpd_client.connect(self.conf["MPD_HOST"], self.conf["MPD_PORT"])
+        except (mpd.ConnectionError, ConnectionRefusedError, FileNotFoundError) as exc:
+            self.log.warning(
+                "Connection to MPD failed (%s: %s)", exc.__class__.__name__, exc
+            )
             raise gevent.GreenletExit()
             raise gevent.GreenletExit()
         return mpd_client
         return mpd_client
 
 
@@ -90,16 +96,17 @@ class Player:
 
 
     @continous_audiospec.setter
     @continous_audiospec.setter
     def continous_audiospec(self, spec):
     def continous_audiospec(self, spec):
-        self._continous_audiospec = self.conf['CONTINOUS_AUDIOSPEC'] \
-                if spec is None else spec
+        self._continous_audiospec = (
+            self.conf["CONTINOUS_AUDIOSPEC"] if spec is None else spec
+        )
 
 
         def clear_everything_but_current_song():
         def clear_everything_but_current_song():
             mpdc = self._get_mpd()
             mpdc = self._get_mpd()
             current = mpdc.currentsong()
             current = mpdc.currentsong()
-            pos = int(current.get('pos', 0))
+            pos = int(current.get("pos", 0))
             for song in mpdc.playlistid():
             for song in mpdc.playlistid():
-                if int(song['pos']) != pos:
-                    mpdc.deleteid(song['id'])
+                if int(song["pos"]) != pos:
+                    mpdc.deleteid(song["id"])
 
 
         gevent.Greenlet.spawn(clear_everything_but_current_song)
         gevent.Greenlet.spawn(clear_everything_but_current_song)
 
 
@@ -107,12 +114,11 @@ class Player:
         mpd_client = self._get_mpd()
         mpd_client = self._get_mpd()
         songs = mpd_client.playlist()
         songs = mpd_client.playlist()
         current = mpd_client.currentsong()
         current = mpd_client.currentsong()
-        pos = int(current.get('pos', 0)) + 1
+        pos = int(current.get("pos", 0)) + 1
         if (len(songs) - pos) >= self.min_playlist_length:
         if (len(songs) - pos) >= self.min_playlist_length:
             return
             return
-        self.log.info('need to add new songs')
-        picker = gevent.Greenlet(audiogenerate,
-                                 self.continous_audiospec)
+        self.log.info("need to add new songs")
+        picker = gevent.Greenlet(audiogenerate, self.continous_audiospec)
 
 
         def add(greenlet):
         def add(greenlet):
             uris = greenlet.value
             uris = greenlet.value
@@ -120,69 +126,71 @@ class Player:
                 assert type(uri) is str, type(uri)
                 assert type(uri) is str, type(uri)
                 self.tmpcleaner.watch(uri.strip())
                 self.tmpcleaner.watch(uri.strip())
                 mpd_client.add(uri.strip())
                 mpd_client.add(uri.strip())
+
         picker.link_value(add)
         picker.link_value(add)
         picker.start()
         picker.start()
 
 
     def enqueue_filter(self, songs):
     def enqueue_filter(self, songs):
-        eventfilters = self.conf['EVENT_FILTERS']
+        eventfilters = self.conf["EVENT_FILTERS"]
         if not eventfilters:
         if not eventfilters:
-            return True, ''
-        availfilters = get_avail_entrypoints('larigira.eventfilter')
+            return True, ""
+        availfilters = get_avail_entrypoints("larigira.eventfilter")
         if len([ef for ef in eventfilters if ef in availfilters]) == 0:
         if len([ef for ef in eventfilters if ef in availfilters]) == 0:
-            return True, ''
+            return True, ""
         mpdc = self._get_mpd()
         mpdc = self._get_mpd()
         status = mpdc.status()
         status = mpdc.status()
-        ctx = {
-            'playlist': mpdc.playlist(),
-            'status': status,
-            'durations': []
-        }
-        for entrypoint in iter_entry_points('larigira.eventfilter'):
+        ctx = {"playlist": mpdc.playlist(), "status": status, "durations": []}
+        for entrypoint in iter_entry_points("larigira.eventfilter"):
             if entrypoint.name in eventfilters:
             if entrypoint.name in eventfilters:
                 ef = entrypoint.load()
                 ef = entrypoint.load()
                 try:
                 try:
                     ret = ef(songs=songs, context=ctx, conf=self.conf)
                     ret = ef(songs=songs, context=ctx, conf=self.conf)
                 except ImportError as exc:
                 except ImportError as exc:
-                    self.log.warn("Filter %s skipped: %s" % (entrypoint.name,
-                                                             exc))
+                    self.log.warn("Filter %s skipped: %s" % (entrypoint.name, exc))
                     continue
                     continue
                 if ret is None:  # bad behavior!
                 if ret is None:  # bad behavior!
                     continue
                     continue
                 if type(ret) is bool:
                 if type(ret) is bool:
-                    reason = ''
+                    reason = ""
                 else:
                 else:
                     ret, reason = ret
                     ret, reason = ret
-                reason = 'Filtered by %s (%s)' % (entrypoint.name, reason)
+                reason = "Filtered by %s (%s)" % (entrypoint.name, reason)
                 if ret is False:
                 if ret is False:
                     return ret, reason
                     return ret, reason
-        return True, 'Passed through %s' % ','.join(availfilters)
+        return True, "Passed through %s" % ",".join(availfilters)
 
 
     def enqueue(self, songs):
     def enqueue(self, songs):
         assert type(songs) is dict
         assert type(songs) is dict
-        assert 'uris' in songs
-        spec = [aspec.get('nick', aspec.eid) for aspec in songs['audiospecs']]
-        nicks = ','.join((aspec.get('nick', aspec.eid)
-                          for aspec in songs['audiospecs']))
+        assert "uris" in songs
+        spec = [aspec.get("nick", aspec.eid) for aspec in songs["audiospecs"]]
+        nicks = ",".join(
+            (aspec.get("nick", aspec.eid) for aspec in songs["audiospecs"])
+        )
         if not self.events_enabled:
         if not self.events_enabled:
-            self.log.debug('Ignoring <%s> (events disabled)', nicks
-                           )
+            self.log.debug("Ignoring <%s> (events disabled)", nicks)
             return
             return
         filterok, reason = self.enqueue_filter(songs)
         filterok, reason = self.enqueue_filter(songs)
         if not filterok:
         if not filterok:
-            self.log.debug('Ignoring <%s>, filtered: %s', nicks, reason)
+            self.log.debug("Ignoring <%s>, filtered: %s", nicks, reason)
             # delete those files
             # delete those files
-            for uri in reversed(songs['uris']):
+            for uri in reversed(songs["uris"]):
                 self.tmpcleaner.watch(uri.strip())
                 self.tmpcleaner.watch(uri.strip())
             return
             return
         mpd_client = self._get_mpd()
         mpd_client = self._get_mpd()
-        for uri in reversed(songs['uris']):
+        for uri in reversed(songs["uris"]):
             assert type(uri) is str
             assert type(uri) is str
-            self.log.info('Adding %s to playlist (from <%s>:%s=%s)',
-                          uri,
-                          songs['timespec'].get('nick', ''),
-                          songs['aids'], spec)
-            insert_pos = 0 if len(mpd_client.playlistid()) == 0 else \
-                int(mpd_client.currentsong().get('pos', 0)) + 1
+            self.log.info(
+                "Adding %s to playlist (from <%s>:%s=%s)",
+                uri,
+                songs["timespec"].get("nick", ""),
+                songs["aids"],
+                spec,
+            )
+            insert_pos = (
+                0
+                if len(mpd_client.playlistid()) == 0
+                else int(mpd_client.currentsong().get("pos", 0)) + 1
+            )
             try:
             try:
                 mpd_client.addid(uri, insert_pos)
                 mpd_client.addid(uri, insert_pos)
             except mpd.CommandError:
             except mpd.CommandError:
@@ -197,7 +205,7 @@ class Controller(gevent.Greenlet):
         self.conf = conf
         self.conf = conf
         self.q = Queue()
         self.q = Queue()
         self.player = Player(self.conf)
         self.player = Player(self.conf)
-        if 'DB_URI' in self.conf:
+        if "DB_URI" in self.conf:
             self.monitor = Monitor(self.q, self.conf)
             self.monitor = Monitor(self.q, self.conf)
             self.monitor.parent_greenlet = self
             self.monitor.parent_greenlet = self
         else:
         else:
@@ -209,28 +217,28 @@ class Controller(gevent.Greenlet):
         mw = MPDWatcher(self.q, self.conf, client=None)
         mw = MPDWatcher(self.q, self.conf, client=None)
         mw.parent_greenlet = self
         mw.parent_greenlet = self
         mw.start()
         mw.start()
-        t = Timer(int(self.conf['CHECK_SECS']) * 1000, self.q)
+        t = Timer(int(self.conf["CHECK_SECS"]) * 1000, self.q)
         t.parent_greenlet = self
         t.parent_greenlet = self
         t.start()
         t.start()
         # at the very start, run a check!
         # at the very start, run a check!
         gevent.Greenlet.spawn(self.player.check_playlist)
         gevent.Greenlet.spawn(self.player.check_playlist)
         while True:
         while True:
             value = self.q.get()
             value = self.q.get()
-            self.log.debug('<- %s', str(value))
+            self.log.debug("<- %s", str(value))
             # emitter = value['emitter']
             # emitter = value['emitter']
-            kind = value['kind']
-            args = value['args']
-            if kind == 'timer' or (kind == 'mpc' and
-                                   args[0] in ('player', 'playlist',
-                                               'connect')):
+            kind = value["kind"]
+            args = value["args"]
+            if kind == "timer" or (
+                kind == "mpc" and args[0] in ("player", "playlist", "connect")
+            ):
                 gevent.Greenlet.spawn(self.player.check_playlist)
                 gevent.Greenlet.spawn(self.player.check_playlist)
                 try:
                 try:
                     self.player.tmpcleaner.check_playlist()
                     self.player.tmpcleaner.check_playlist()
                 except:
                 except:
                     pass
                     pass
-            elif kind == 'mpc':
+            elif kind == "mpc":
                 pass
                 pass
-            elif kind == 'uris_enqueue':
+            elif kind == "uris_enqueue":
                 # TODO: uris_enqueue messages should be delivered directly to Player.enqueue
                 # TODO: uris_enqueue messages should be delivered directly to Player.enqueue
                 #   probably we need a MPDEnqueuer that receives every uri we want to add
                 #   probably we need a MPDEnqueuer that receives every uri we want to add
                 try:
                 try:
@@ -238,13 +246,13 @@ class Controller(gevent.Greenlet):
                 except AssertionError:
                 except AssertionError:
                     raise
                     raise
                 except Exception:
                 except Exception:
-                    self.log.exception("Error while adding to queue; "
-                                       "bad audiogen output?")
-            elif (kind == 'signal' and args[0] == signal.SIGALRM) or \
-                    kind == 'refresh':
+                    self.log.exception(
+                        "Error while adding to queue; " "bad audiogen output?"
+                    )
+            elif (kind == "signal" and args[0] == signal.SIGALRM) or kind == "refresh":
                 # it's a tick!
                 # it's a tick!
                 self.log.debug("Reload")
                 self.log.debug("Reload")
-                self.monitor.q.put(dict(kind='forcetick'))
+                self.monitor.q.put(dict(kind="forcetick"))
                 gevent.Greenlet.spawn(self.player.check_playlist)
                 gevent.Greenlet.spawn(self.player.check_playlist)
             else:
             else:
                 self.log.warning("Unknown message: %s", str(value))
                 self.log.warning("Unknown message: %s", str(value))

+ 8 - 7
larigira/test_unused.py

@@ -6,26 +6,27 @@ from .config import get_conf
 
 
 @pytest.fixture
 @pytest.fixture
 def unusedcleaner():
 def unusedcleaner():
-    return UnusedCleaner(get_conf(prefix='LARIGIRATEST_'))
+    return UnusedCleaner(get_conf(prefix="LARIGIRATEST_"))
 
 
 
 
 # this test suite heavily assumes that TMPDIR == /tmp/, which is the default
 # this test suite heavily assumes that TMPDIR == /tmp/, which is the default
 # indeed.  However, the code does not rely on this assumption.
 # indeed.  However, the code does not rely on this assumption.
 
 
+
 def test_watch_file(unusedcleaner):
 def test_watch_file(unusedcleaner):
     # despite not existing, the file is added
     # despite not existing, the file is added
-    unusedcleaner.watch('file:///tmp/gnam')
+    unusedcleaner.watch("file:///tmp/gnam")
     assert len(unusedcleaner.waiting_removal_files) == 1
     assert len(unusedcleaner.waiting_removal_files) == 1
-    assert list(unusedcleaner.waiting_removal_files)[0] == '/tmp/gnam'
+    assert list(unusedcleaner.waiting_removal_files)[0] == "/tmp/gnam"
 
 
 
 
 def test_watch_path_error(unusedcleaner):
 def test_watch_path_error(unusedcleaner):
-    '''paths are not valid thing to watch. URIs only, thanks'''
-    unusedcleaner.watch('/tmp/foo')
+    """paths are not valid thing to watch. URIs only, thanks"""
+    unusedcleaner.watch("/tmp/foo")
     assert len(unusedcleaner.waiting_removal_files) == 0
     assert len(unusedcleaner.waiting_removal_files) == 0
 
 
 
 
 def test_watch_notmp_error(unusedcleaner):
 def test_watch_notmp_error(unusedcleaner):
-    '''Files not in TMPDIR are not added'''
-    unusedcleaner.watch('file:///not/in/tmp')
+    """Files not in TMPDIR are not added"""
+    unusedcleaner.watch("file:///not/in/tmp")
     assert len(unusedcleaner.waiting_removal_files) == 0
     assert len(unusedcleaner.waiting_removal_files) == 0

+ 11 - 11
larigira/tests/test_audiogen_mostrecent.py

@@ -14,19 +14,19 @@ def now(request):
 
 
 @pytest.fixture
 @pytest.fixture
 def yesterday(request):
 def yesterday(request):
-    return int(time.time()) - 24*60*60
+    return int(time.time()) - 24 * 60 * 60
 
 
 
 
 @pytest.fixture
 @pytest.fixture
 def empty_dir():
 def empty_dir():
-    dirpath = tempfile.mkdtemp(prefix='mostrecent.')
+    dirpath = tempfile.mkdtemp(prefix="mostrecent.")
     yield dirpath
     yield dirpath
     os.removedirs(dirpath)
     os.removedirs(dirpath)
 
 
 
 
 @pytest.fixture
 @pytest.fixture
 def dir_with_old_file(empty_dir):
 def dir_with_old_file(empty_dir):
-    fd, fname = tempfile.mkstemp(prefix='old.', dir=empty_dir)
+    fd, fname = tempfile.mkstemp(prefix="old.", dir=empty_dir)
     os.close(fd)
     os.close(fd)
     os.utime(fname, times=(0, 0))
     os.utime(fname, times=(0, 0))
     yield empty_dir
     yield empty_dir
@@ -35,7 +35,7 @@ def dir_with_old_file(empty_dir):
 
 
 @pytest.fixture
 @pytest.fixture
 def dir_with_yesterday_file(empty_dir, yesterday):
 def dir_with_yesterday_file(empty_dir, yesterday):
-    fd, fname = tempfile.mkstemp(prefix='yesterday.', dir=empty_dir)
+    fd, fname = tempfile.mkstemp(prefix="yesterday.", dir=empty_dir)
     os.close(fd)
     os.close(fd)
     os.utime(fname, times=(yesterday, yesterday))
     os.utime(fname, times=(yesterday, yesterday))
     yield empty_dir
     yield empty_dir
@@ -44,7 +44,7 @@ def dir_with_yesterday_file(empty_dir, yesterday):
 
 
 @pytest.fixture
 @pytest.fixture
 def dir_with_new_file(dir_with_old_file, now):
 def dir_with_new_file(dir_with_old_file, now):
-    fd, fname = tempfile.mkstemp(prefix='new.', dir=dir_with_old_file)
+    fd, fname = tempfile.mkstemp(prefix="new.", dir=dir_with_old_file)
     os.close(fd)
     os.close(fd)
     os.utime(fname, times=(now, now))
     os.utime(fname, times=(now, now))
     yield dir_with_old_file
     yield dir_with_old_file
@@ -53,7 +53,7 @@ def dir_with_new_file(dir_with_old_file, now):
 
 
 @pytest.fixture
 @pytest.fixture
 def dir_with_two_recent_files(dir_with_yesterday_file, now):
 def dir_with_two_recent_files(dir_with_yesterday_file, now):
-    fd, fname = tempfile.mkstemp(prefix='new.', dir=dir_with_yesterday_file)
+    fd, fname = tempfile.mkstemp(prefix="new.", dir=dir_with_yesterday_file)
     os.close(fd)
     os.close(fd)
     os.utime(fname, times=(now, now))
     os.utime(fname, times=(now, now))
     yield dir_with_yesterday_file
     yield dir_with_yesterday_file
@@ -61,7 +61,7 @@ def dir_with_two_recent_files(dir_with_yesterday_file, now):
 
 
 
 
 def test_empty_is_empty(empty_dir, now):
 def test_empty_is_empty(empty_dir, now):
-    '''nothing can be picked from a empty dir'''
+    """nothing can be picked from a empty dir"""
     picked = recent_choose([empty_dir], 1, now)
     picked = recent_choose([empty_dir], 1, now)
     assert len(picked) == 0
     assert len(picked) == 0
 
 
@@ -74,17 +74,17 @@ def test_old_files(dir_with_old_file, now):
 def test_new_files_found(dir_with_new_file):
 def test_new_files_found(dir_with_new_file):
     picked = recent_choose([dir_with_new_file], 1, 1)
     picked = recent_choose([dir_with_new_file], 1, 1)
     assert len(picked) == 1
     assert len(picked) == 1
-    assert os.path.basename(picked[0]).startswith('new.')
+    assert os.path.basename(picked[0]).startswith("new.")
 
 
 
 
 def test_only_new_files_found(dir_with_new_file):
 def test_only_new_files_found(dir_with_new_file):
     picked = recent_choose([dir_with_new_file], 2, 1)
     picked = recent_choose([dir_with_new_file], 2, 1)
     assert len(picked) == 1
     assert len(picked) == 1
-    assert os.path.basename(picked[0]).startswith('new.')
+    assert os.path.basename(picked[0]).startswith("new.")
 
 
 
 
 def test_correct_sorting(dir_with_two_recent_files):
 def test_correct_sorting(dir_with_two_recent_files):
     picked = recent_choose([dir_with_two_recent_files], 1, 1)
     picked = recent_choose([dir_with_two_recent_files], 1, 1)
     assert len(picked) == 1
     assert len(picked) == 1
-    assert not os.path.basename(picked[0]).startswith('yesterday.')
-    assert os.path.basename(picked[0]).startswith('new.')
+    assert not os.path.basename(picked[0]).startswith("yesterday.")
+    assert os.path.basename(picked[0]).startswith("new.")

+ 3 - 3
larigira/tests/test_audiogen_mpdrandom.py

@@ -1,5 +1,6 @@
 from __future__ import print_function
 from __future__ import print_function
 from gevent import monkey
 from gevent import monkey
+
 monkey.patch_all(subprocess=True)
 monkey.patch_all(subprocess=True)
 
 
 import pytest
 import pytest
@@ -9,10 +10,9 @@ from larigira.audiogen_mpdrandom import generate_by_artist
 
 
 @pytest.fixture
 @pytest.fixture
 def simplerandom():
 def simplerandom():
-    return {
-    }
+    return {}
 
 
 
 
 def test_accepted_syntax(simplerandom):
 def test_accepted_syntax(simplerandom):
-    '''Check the minimal needed configuration for mpdrandom'''
+    """Check the minimal needed configuration for mpdrandom"""
     generate_by_artist(simplerandom)
     generate_by_artist(simplerandom)

+ 13 - 13
larigira/tests/test_audiogen_randomdir.py

@@ -9,51 +9,51 @@ def P(pypathlocal):
 
 
 def test_txt_files_are_excluded(tmpdir):
 def test_txt_files_are_excluded(tmpdir):
     p = tmpdir.join("foo.txt")
     p = tmpdir.join("foo.txt")
-    p.write('')
+    p.write("")
     assert len(candidates([P(p)])) == 0
     assert len(candidates([P(p)])) == 0
     assert len(candidates([P(tmpdir)])) == 0
     assert len(candidates([P(tmpdir)])) == 0
 
 
 
 
 def test_nested_txt_files_are_excluded(tmpdir):
 def test_nested_txt_files_are_excluded(tmpdir):
-    p = tmpdir.mkdir('one').mkdir('two').join("foo.txt")
-    p.write('')
+    p = tmpdir.mkdir("one").mkdir("two").join("foo.txt")
+    p.write("")
     assert len(candidates([P(p)])) == 0
     assert len(candidates([P(p)])) == 0
     assert len(candidates([P(tmpdir)])) == 0
     assert len(candidates([P(tmpdir)])) == 0
 
 
 
 
 def test_mp3_files_are_considered(tmpdir):
 def test_mp3_files_are_considered(tmpdir):
     p = tmpdir.join("foo.mp3")
     p = tmpdir.join("foo.mp3")
-    p.write('')
+    p.write("")
     assert len(candidates([P(p)])) == 1
     assert len(candidates([P(p)])) == 1
     assert len(candidates([P(tmpdir)])) == 1
     assert len(candidates([P(tmpdir)])) == 1
 
 
 
 
 def test_nested_mp3_files_are_considered(tmpdir):
 def test_nested_mp3_files_are_considered(tmpdir):
-    p = tmpdir.mkdir('one').mkdir('two').join("foo.mp3")
-    p.write('')
+    p = tmpdir.mkdir("one").mkdir("two").join("foo.mp3")
+    p.write("")
     assert len(candidates([P(p)])) == 1
     assert len(candidates([P(p)])) == 1
     assert len(candidates([P(tmpdir)])) == 1
     assert len(candidates([P(tmpdir)])) == 1
 
 
 
 
 def test_same_name(tmpdir):
 def test_same_name(tmpdir):
-    '''file with same name on different dir should not be confused'''
-    p = tmpdir.mkdir('one').mkdir('two').join("foo.mp3")
-    p.write('')
+    """file with same name on different dir should not be confused"""
+    p = tmpdir.mkdir("one").mkdir("two").join("foo.mp3")
+    p.write("")
     p = tmpdir.join("foo.mp3")
     p = tmpdir.join("foo.mp3")
-    p.write('')
+    p.write("")
 
 
     assert len(candidates([P(tmpdir)])) == 2
     assert len(candidates([P(tmpdir)])) == 2
 
 
 
 
 def test_unknown_mime_ignore(tmpdir):
 def test_unknown_mime_ignore(tmpdir):
     p = tmpdir.join("foo.???")
     p = tmpdir.join("foo.???")
-    p.write('')
+    p.write("")
     assert len(candidates([P(tmpdir)])) == 0
     assert len(candidates([P(tmpdir)])) == 0
 
 
 
 
 def test_unknown_mime_nocrash(tmpdir):
 def test_unknown_mime_nocrash(tmpdir):
     p = tmpdir.join("foo.???")
     p = tmpdir.join("foo.???")
-    p.write('')
+    p.write("")
     p = tmpdir.join("foo.ogg")
     p = tmpdir.join("foo.ogg")
-    p.write('')
+    p.write("")
     assert len(candidates([P(tmpdir)])) == 1
     assert len(candidates([P(tmpdir)])) == 1

+ 13 - 11
larigira/tests/test_commonpath.py

@@ -4,14 +4,14 @@ from larigira.unused import old_commonpath
 
 
 
 
 def test_same():
 def test_same():
-    assert old_commonpath(['/foo/bar', '/foo/bar/']) == '/foo/bar'
+    assert old_commonpath(["/foo/bar", "/foo/bar/"]) == "/foo/bar"
 
 
 
 
 def test_prefix():
 def test_prefix():
-    assert old_commonpath(['/foo/bar', '/foo/zap/']) == '/foo'
-    assert old_commonpath(['/foo/bar/', '/foo/zap/']) == '/foo'
-    assert old_commonpath(['/foo/bar/', '/foo/zap']) == '/foo'
-    assert old_commonpath(['/foo/bar', '/foo/zap']) == '/foo'
+    assert old_commonpath(["/foo/bar", "/foo/zap/"]) == "/foo"
+    assert old_commonpath(["/foo/bar/", "/foo/zap/"]) == "/foo"
+    assert old_commonpath(["/foo/bar/", "/foo/zap"]) == "/foo"
+    assert old_commonpath(["/foo/bar", "/foo/zap"]) == "/foo"
 
 
 
 
 try:
 try:
@@ -22,17 +22,18 @@ else:
     # these tests are only available on python >= 3.5. That's fine though, as
     # these tests are only available on python >= 3.5. That's fine though, as
     # our CI will perform validation of those cases to see if they match python
     # our CI will perform validation of those cases to see if they match python
     # behavior
     # behavior
-    @pytest.fixture(params=['a', 'a/', 'a/b', 'a/b/', 'a/b/c', ])
+    @pytest.fixture(params=["a", "a/", "a/b", "a/b/", "a/b/c"])
     def relpath(request):
     def relpath(request):
         return request.param
         return request.param
 
 
     @pytest.fixture
     @pytest.fixture
     def abspath(relpath):
     def abspath(relpath):
-        return '/' + relpath
+        return "/" + relpath
 
 
-    @pytest.fixture(params=['', '/'])
+    @pytest.fixture(params=["", "/"])
     def slashed_abspath(abspath, request):
     def slashed_abspath(abspath, request):
-        return '%s%s' % (abspath, request.param)
+        return "%s%s" % (abspath, request.param)
+
     slashed_abspath_b = slashed_abspath
     slashed_abspath_b = slashed_abspath
 
 
     @pytest.fixture
     @pytest.fixture
@@ -42,11 +43,12 @@ else:
     def test_abspath_match(abspath_couple):
     def test_abspath_match(abspath_couple):
         assert commonpath(abspath_couple) == old_commonpath(abspath_couple)
         assert commonpath(abspath_couple) == old_commonpath(abspath_couple)
 
 
-    @pytest.fixture(params=['', '/'])
+    @pytest.fixture(params=["", "/"])
     def slashed_relpath(relpath, request):
     def slashed_relpath(relpath, request):
-        s = '%s%s' % (relpath, request.param)
+        s = "%s%s" % (relpath, request.param)
         if s:
         if s:
             return s
             return s
+
     slashed_relpath_b = slashed_relpath
     slashed_relpath_b = slashed_relpath
 
 
     @pytest.fixture
     @pytest.fixture

+ 21 - 15
larigira/tests/test_db.py

@@ -2,6 +2,7 @@ import tempfile
 import os
 import os
 
 
 from gevent import monkey
 from gevent import monkey
+
 monkey.patch_all(subprocess=True)
 monkey.patch_all(subprocess=True)
 
 
 import pytest
 import pytest
@@ -11,7 +12,7 @@ from larigira.db import EventModel
 
 
 @pytest.yield_fixture
 @pytest.yield_fixture
 def db():
 def db():
-    fname = tempfile.mktemp(suffix='.json', prefix='larigira-test')
+    fname = tempfile.mktemp(suffix=".json", prefix="larigira-test")
     yield EventModel(uri=fname)
     yield EventModel(uri=fname)
     os.unlink(fname)
     os.unlink(fname)
 
 
@@ -22,36 +23,39 @@ def test_empty(db):
 
 
 def test_add_basic(db):
 def test_add_basic(db):
     assert len(db.get_all_alarms()) == 0
     assert len(db.get_all_alarms()) == 0
-    alarm_id = db.add_event(dict(kind='frequency', interval=60*3, start=1),
-                            [dict(kind='mpd', paths=['foo.mp3'], howmany=1)])
+    alarm_id = db.add_event(
+        dict(kind="frequency", interval=60 * 3, start=1),
+        [dict(kind="mpd", paths=["foo.mp3"], howmany=1)],
+    )
     assert len(db.get_all_alarms()) == 1
     assert len(db.get_all_alarms()) == 1
     assert db.get_alarm_by_id(alarm_id) is not None
     assert db.get_alarm_by_id(alarm_id) is not None
-    assert len(tuple(db.get_actions_by_alarm(
-        db.get_alarm_by_id(alarm_id)))) == 1
+    assert len(tuple(db.get_actions_by_alarm(db.get_alarm_by_id(alarm_id)))) == 1
 
 
 
 
 def test_add_multiple_alarms(db):
 def test_add_multiple_alarms(db):
     assert len(db.get_all_alarms()) == 0
     assert len(db.get_all_alarms()) == 0
-    alarm_id = db.add_event(dict(kind='frequency', interval=60*3, start=1),
-                            [dict(kind='mpd', paths=['foo.mp3'], howmany=1),
-                             dict(kind='foo', a=3)])
+    alarm_id = db.add_event(
+        dict(kind="frequency", interval=60 * 3, start=1),
+        [dict(kind="mpd", paths=["foo.mp3"], howmany=1), dict(kind="foo", a=3)],
+    )
     assert len(db.get_all_alarms()) == 1
     assert len(db.get_all_alarms()) == 1
     assert db.get_alarm_by_id(alarm_id) is not None
     assert db.get_alarm_by_id(alarm_id) is not None
     assert len(db.get_all_actions()) == 2
     assert len(db.get_all_actions()) == 2
-    assert len(tuple(db.get_actions_by_alarm(
-        db.get_alarm_by_id(alarm_id)))) == 2
+    assert len(tuple(db.get_actions_by_alarm(db.get_alarm_by_id(alarm_id)))) == 2
 
 
 
 
 def test_delete_alarm(db):
 def test_delete_alarm(db):
     assert len(db.get_all_alarms()) == 0
     assert len(db.get_all_alarms()) == 0
-    alarm_id = db.add_event(dict(kind='frequency', interval=60*3, start=1),
-                            [dict(kind='mpd', paths=['foo.mp3'], howmany=1)])
+    alarm_id = db.add_event(
+        dict(kind="frequency", interval=60 * 3, start=1),
+        [dict(kind="mpd", paths=["foo.mp3"], howmany=1)],
+    )
     action_id = next(db.get_actions_by_alarm(db.get_alarm_by_id(alarm_id))).eid
     action_id = next(db.get_actions_by_alarm(db.get_alarm_by_id(alarm_id))).eid
     assert len(db.get_all_alarms()) == 1
     assert len(db.get_all_alarms()) == 1
     db.delete_alarm(alarm_id)
     db.delete_alarm(alarm_id)
     assert len(db.get_all_alarms()) == 0  # alarm deleted
     assert len(db.get_all_alarms()) == 0  # alarm deleted
     assert db.get_action_by_id(action_id) is not None
     assert db.get_action_by_id(action_id) is not None
-    assert 'kind' in db.get_action_by_id(action_id)  # action still there
+    assert "kind" in db.get_action_by_id(action_id)  # action still there
 
 
 
 
 def test_delete_alarm_nonexisting(db):
 def test_delete_alarm_nonexisting(db):
@@ -60,8 +64,10 @@ def test_delete_alarm_nonexisting(db):
 
 
 
 
 def test_delete_action(db):
 def test_delete_action(db):
-    alarm_id = db.add_event(dict(kind='frequency', interval=60*3, start=1),
-                            [dict(kind='mpd', paths=['foo.mp3'], howmany=1)])
+    alarm_id = db.add_event(
+        dict(kind="frequency", interval=60 * 3, start=1),
+        [dict(kind="mpd", paths=["foo.mp3"], howmany=1)],
+    )
     alarm = db.get_alarm_by_id(alarm_id)
     alarm = db.get_alarm_by_id(alarm_id)
     assert len(tuple(db.get_actions_by_alarm(alarm))) == 1
     assert len(tuple(db.get_actions_by_alarm(alarm))) == 1
     action = next(db.get_actions_by_alarm(alarm))
     action = next(db.get_actions_by_alarm(alarm))

+ 6 - 6
larigira/tests/test_fsutils.py

@@ -2,15 +2,15 @@ from larigira.fsutils import shortname
 
 
 
 
 def test_shortname_self():
 def test_shortname_self():
-    '''sometimes, shortname is just filename without extension'''
-    assert shortname('/tmp/asd/foo.bar') == 'foo'
+    """sometimes, shortname is just filename without extension"""
+    assert shortname("/tmp/asd/foo.bar") == "foo"
 
 
 
 
 def test_shortname_has_numbers():
 def test_shortname_has_numbers():
-    '''shortname will preserve numbers'''
-    assert shortname('/tmp/asd/foo1.bar') == 'foo1'
+    """shortname will preserve numbers"""
+    assert shortname("/tmp/asd/foo1.bar") == "foo1"
 
 
 
 
 def test_shortname_has_no_hyphen():
 def test_shortname_has_no_hyphen():
-    '''shortname will not preserve hyphens'''
-    assert shortname('/tmp/asd/foo-1.bar') == 'foo1'
+    """shortname will not preserve hyphens"""
+    assert shortname("/tmp/asd/foo-1.bar") == "foo1"

+ 12 - 9
larigira/tests/test_parented.py

@@ -1,5 +1,6 @@
 from __future__ import print_function
 from __future__ import print_function
 from gevent import monkey
 from gevent import monkey
+
 monkey.patch_all(subprocess=True)
 monkey.patch_all(subprocess=True)
 
 
 import pytest
 import pytest
@@ -21,7 +22,8 @@ def range_parentlet():
 
 
         def do_business(self):
         def do_business(self):
             for i in range(self.howmany):
             for i in range(self.howmany):
-                yield('range', i)
+                yield ("range", i)
+
     return RangeLet
     return RangeLet
 
 
 
 
@@ -33,7 +35,8 @@ def single_value_parentlet():
             self.val = val
             self.val = val
 
 
         def do_business(self):
         def do_business(self):
-            yield('single', self.val)
+            yield ("single", self.val)
+
     return SingleLet
     return SingleLet
 
 
 
 
@@ -44,7 +47,7 @@ def test_parented_range(queue, range_parentlet):
     assert queue.qsize() == 5
     assert queue.qsize() == 5
     while not queue.empty():
     while not queue.empty():
         msg = queue.get()
         msg = queue.get()
-        assert msg['kind'] == 'range'
+        assert msg["kind"] == "range"
 
 
 
 
 def test_parented_single(queue, single_value_parentlet):
 def test_parented_single(queue, single_value_parentlet):
@@ -52,25 +55,25 @@ def test_parented_single(queue, single_value_parentlet):
     t.start()
     t.start()
     gevent.sleep(0.01)
     gevent.sleep(0.01)
     msg = queue.get_nowait()
     msg = queue.get_nowait()
-    assert msg['args'][0] == 123
+    assert msg["args"][0] == 123
 
 
 
 
 def test_timer_finally(queue):
 def test_timer_finally(queue):
-    '''at somepoint, it will get results'''
+    """at somepoint, it will get results"""
     period = 10
     period = 10
     t = Timer(period, queue)
     t = Timer(period, queue)
     t.start()
     t.start()
-    gevent.sleep(period*3/1000.0)
+    gevent.sleep(period * 3 / 1000.0)
     queue.get_nowait()
     queue.get_nowait()
 
 
 
 
 def test_timer_righttime(queue):
 def test_timer_righttime(queue):
-    '''not too early, not too late'''
+    """not too early, not too late"""
     period = 500
     period = 500
     t = Timer(period, queue)
     t = Timer(period, queue)
     t.start()
     t.start()
-    gevent.sleep(period/(10*1000.0))
+    gevent.sleep(period / (10 * 1000.0))
     assert queue.empty() is True
     assert queue.empty() is True
-    gevent.sleep(period*(2/1000.0))
+    gevent.sleep(period * (2 / 1000.0))
     assert not queue.empty()
     assert not queue.empty()
     queue.get_nowait()
     queue.get_nowait()

+ 47 - 69
larigira/tests/test_time_every.py

@@ -8,7 +8,7 @@ from larigira.timegen import timegenerate
 
 
 
 
 def eq_(a, b, reason=None):
 def eq_(a, b, reason=None):
-    '''migrating tests from nose'''
+    """migrating tests from nose"""
     if reason is not None:
     if reason is not None:
         assert a == b, reason
         assert a == b, reason
     else:
     else:
@@ -20,63 +20,55 @@ def now():
     return datetime.now()
     return datetime.now()
 
 
 
 
-@pytest.fixture(params=['seconds', 'human', 'humanlong', 'coloned'])
+@pytest.fixture(params=["seconds", "human", "humanlong", "coloned"])
 def onehour(now, request):
 def onehour(now, request):
-    '''a FrequencyAlarm: every hour for one day'''
-    intervals = dict(seconds=3600, human='1h', humanlong='30m 1800s',
-                     coloned='01:00:00')
-    return FrequencyAlarm({
-        'start': now - timedelta(days=1),
-        'interval': intervals[request.param],
-        'end': now + days(1)})
-
-
-@pytest.fixture(params=[1, '1'])
+    """a FrequencyAlarm: every hour for one day"""
+    intervals = dict(
+        seconds=3600, human="1h", humanlong="30m 1800s", coloned="01:00:00"
+    )
+    return FrequencyAlarm(
+        {
+            "start": now - timedelta(days=1),
+            "interval": intervals[request.param],
+            "end": now + days(1),
+        }
+    )
+
+
+@pytest.fixture(params=[1, "1"])
 def onehour_monday(request):
 def onehour_monday(request):
     weekday = request.param
     weekday = request.param
-    yield FrequencyAlarm({
-        'interval': 3600*12,
-        'weekdays': [weekday],
-        'start': 0
-    })
+    yield FrequencyAlarm({"interval": 3600 * 12, "weekdays": [weekday], "start": 0})
 
 
 
 
-@pytest.fixture(params=[7, '7'])
+@pytest.fixture(params=[7, "7"])
 def onehour_sunday(request):
 def onehour_sunday(request):
     weekday = request.param
     weekday = request.param
-    yield FrequencyAlarm({
-        'interval': 3600*12,
-        'weekdays': [weekday],
-        'start': 0
-    })
+    yield FrequencyAlarm({"interval": 3600 * 12, "weekdays": [weekday], "start": 0})
 
 
 
 
 @pytest.fixture(params=[1, 2, 3, 4, 5, 6, 7])
 @pytest.fixture(params=[1, 2, 3, 4, 5, 6, 7])
 def singledow(request):
 def singledow(request):
     weekday = request.param
     weekday = request.param
-    yield FrequencyAlarm({
-        'interval': 3600*24,
-        'weekdays': [weekday],
-        'start': 0
-    })
+    yield FrequencyAlarm({"interval": 3600 * 24, "weekdays": [weekday], "start": 0})
 
 
 
 
-@pytest.fixture(params=['seconds', 'human', 'coloned'])
+@pytest.fixture(params=["seconds", "human", "coloned"])
 def tenseconds(now, request):
 def tenseconds(now, request):
-    '''a FrequencyAlarm: every 10 seconds for one day'''
-    intervals = dict(seconds=10, human='10s', coloned='00:10')
-    return FrequencyAlarm({
-        'start': now - timedelta(days=1),
-        'interval': intervals[request.param],
-        'end': now + days(1)})
+    """a FrequencyAlarm: every 10 seconds for one day"""
+    intervals = dict(seconds=10, human="10s", coloned="00:10")
+    return FrequencyAlarm(
+        {
+            "start": now - timedelta(days=1),
+            "interval": intervals[request.param],
+            "end": now + days(1),
+        }
+    )
 
 
 
 
 @pytest.fixture(params=[1, 2, 3, 4, 5, 6, 7, 8])
 @pytest.fixture(params=[1, 2, 3, 4, 5, 6, 7, 8])
 def manyweeks(request):
 def manyweeks(request):
-    yield FrequencyAlarm({
-        'interval': '{}w'.format(request.param),
-        'start': 0
-    })
+    yield FrequencyAlarm({"interval": "{}w".format(request.param), "start": 0})
 
 
 
 
 def days(n):
 def days(n):
@@ -84,24 +76,21 @@ def days(n):
 
 
 
 
 def test_single_creations(now):
 def test_single_creations(now):
-    return SingleAlarm({
-        'timestamp': now
-    })
+    return SingleAlarm({"timestamp": now})
 
 
 
 
 def test_freq_creations(now):
 def test_freq_creations(now):
-    return FrequencyAlarm({
-        'start': now - timedelta(days=1),
-        'interval': 3600,
-        'end': now})
+    return FrequencyAlarm(
+        {"start": now - timedelta(days=1), "interval": 3600, "end": now}
+    )
 
 
 
 
 @pytest.mark.timeout(1)
 @pytest.mark.timeout(1)
 def test_single_ring(now):
 def test_single_ring(now):
     dt = now + days(1)
     dt = now + days(1)
-    s = SingleAlarm({'timestamp': dt})
-    eq_(s.next_ring(),  dt)
-    eq_(s.next_ring(now),  dt)
+    s = SingleAlarm({"timestamp": dt})
+    eq_(s.next_ring(), dt)
+    eq_(s.next_ring(now), dt)
     assert s.next_ring(dt) is None, "%s - %s" % (str(s.next_ring(dt)), str(dt))
     assert s.next_ring(dt) is None, "%s - %s" % (str(s.next_ring(dt)), str(dt))
     assert s.next_ring(now + days(2)) is None
     assert s.next_ring(now + days(2)) is None
     assert s.has_ring(dt)
     assert s.has_ring(dt)
@@ -112,10 +101,10 @@ def test_single_ring(now):
 @pytest.mark.timeout(1)
 @pytest.mark.timeout(1)
 def test_single_all(now):
 def test_single_all(now):
     dt = now + timedelta(days=1)
     dt = now + timedelta(days=1)
-    s = SingleAlarm({'timestamp': dt})
-    eq_(list(s.all_rings()),  [dt])
-    eq_(list(s.all_rings(now)),  [dt])
-    eq_(list(s.all_rings(now + days(2))),  [])
+    s = SingleAlarm({"timestamp": dt})
+    eq_(list(s.all_rings()), [dt])
+    eq_(list(s.all_rings(now)), [dt])
+    eq_(list(s.all_rings(now + days(2))), [])
 
 
 
 
 def test_freq_short(now, tenseconds):
 def test_freq_short(now, tenseconds):
@@ -165,12 +154,8 @@ def test_weekday_skip_2(onehour_sunday):
 
 
 def test_sunday_is_not_0():
 def test_sunday_is_not_0():
     with pytest.raises(ValueError) as excinfo:
     with pytest.raises(ValueError) as excinfo:
-        FrequencyAlarm({
-                'interval': 3600*12,
-                'weekdays': [0],
-                'start': 0
-            })
-    assert 'Not a valid weekday:' in excinfo.value.args[0]
+        FrequencyAlarm({"interval": 3600 * 12, "weekdays": [0], "start": 0})
+    assert "Not a valid weekday:" in excinfo.value.args[0]
 
 
 
 
 def test_long_interval(manyweeks):
 def test_long_interval(manyweeks):
@@ -178,7 +163,7 @@ def test_long_interval(manyweeks):
     expected = manyweeks.interval
     expected = manyweeks.interval
     got = manyweeks.next_ring(t)
     got = manyweeks.next_ring(t)
     assert got is not None
     assert got is not None
-    assert int(got.strftime('%s')) == expected
+    assert int(got.strftime("%s")) == expected
     assert manyweeks.next_ring(got) is not None
     assert manyweeks.next_ring(got) is not None
 
 
 
 
@@ -194,15 +179,8 @@ def test_singledow(singledow):
 
 
 
 
 def test_single_registered():
 def test_single_registered():
-    timegenerate({
-        'kind': 'single',
-        'timestamp': 1234567890
-    })
+    timegenerate({"kind": "single", "timestamp": 1234567890})
 
 
 
 
 def test_frequency_registered():
 def test_frequency_registered():
-    timegenerate({
-        'kind': 'frequency',
-        'start': 1234567890,
-        'interval': 60*15
-    })
+    timegenerate({"kind": "frequency", "start": 1234567890, "interval": 60 * 15})

+ 2 - 1
larigira/tests/test_web.py

@@ -1,5 +1,6 @@
 from __future__ import print_function
 from __future__ import print_function
 from gevent import monkey
 from gevent import monkey
+
 monkey.patch_all(subprocess=True)
 monkey.patch_all(subprocess=True)
 
 
 import pytest
 import pytest
@@ -15,5 +16,5 @@ def app(queue):
 
 
 def test_refresh(app):
 def test_refresh(app):
     assert app.queue.empty()
     assert app.queue.empty()
-    app.test_client().get('/api/refresh')
+    app.test_client().get("/api/refresh")
     assert not app.queue.empty()
     assert not app.queue.empty()

+ 88 - 64
larigira/timeform_base.py

@@ -4,101 +4,125 @@ from datetime import datetime
 from pytimeparse.timeparse import timeparse
 from pytimeparse.timeparse import timeparse
 
 
 from flask_wtf import Form
 from flask_wtf import Form
-from wtforms import StringField, validators, SubmitField, \
-        SelectMultipleField, ValidationError
+from wtforms import (
+    StringField,
+    validators,
+    SubmitField,
+    SelectMultipleField,
+    ValidationError,
+)
 
 
 from larigira.formutils import EasyDateTimeField
 from larigira.formutils import EasyDateTimeField
+
 log = logging.getLogger(__name__)
 log = logging.getLogger(__name__)
 
 
 
 
 class SingleAlarmForm(Form):
 class SingleAlarmForm(Form):
-    nick = StringField('Alarm nick', validators=[validators.required()],
-                       description='A simple name to recognize this alarm')
-    dt = EasyDateTimeField('Date and time', validators=[validators.required()],
-                           description='Date to ring on, expressed as '
-                           '2000-12-31T13:42:00')
-    submit = SubmitField('Submit')
+    nick = StringField(
+        "Alarm nick",
+        validators=[validators.required()],
+        description="A simple name to recognize this alarm",
+    )
+    dt = EasyDateTimeField(
+        "Date and time",
+        validators=[validators.required()],
+        description="Date to ring on, expressed as " "2000-12-31T13:42:00",
+    )
+    submit = SubmitField("Submit")
 
 
     def populate_from_timespec(self, timespec):
     def populate_from_timespec(self, timespec):
-        if 'nick' in timespec:
-            self.nick.data = timespec['nick']
-        if 'timestamp' in timespec:
-            self.dt.data = datetime.fromtimestamp(timespec['timestamp'])
+        if "nick" in timespec:
+            self.nick.data = timespec["nick"]
+        if "timestamp" in timespec:
+            self.dt.data = datetime.fromtimestamp(timespec["timestamp"])
 
 
 
 
 def singlealarm_receive(form):
 def singlealarm_receive(form):
     return {
     return {
-        'kind': 'single',
-        'nick': form.nick.data,
-        'timestamp': int(form.dt.data.strftime('%s'))
+        "kind": "single",
+        "nick": form.nick.data,
+        "timestamp": int(form.dt.data.strftime("%s")),
     }
     }
 
 
 
 
 class FrequencyAlarmForm(Form):
 class FrequencyAlarmForm(Form):
-    nick = StringField('Alarm nick', validators=[validators.required()],
-                       description='A simple name to recognize this alarm')
-    interval = StringField('Frequency',
-                           validators=[validators.required()],
-                           description='in seconds, or human-readable '
-                           '(like 9w3d12h)')
-    start = EasyDateTimeField('Start date and time',
-                              validators=[validators.optional()],
-                              description='Before this, no alarm will ring. '
-                              'Expressed as YYYY-MM-DDTHH:MM:SS. If omitted, '
-                              'the alarm will always ring')
-    end = EasyDateTimeField('End date and time',
-                            validators=[validators.optional()],
-                            description='After this, no alarm will ring. '
-                            'Expressed as YYYY-MM-DDTHH:MM:SS. If omitted, '
-                            'the alarm will always ring')
-    weekdays = SelectMultipleField('Days on which the alarm should be played',
-                                   choices=[('1', 'Monday'),
-                                            ('2', 'Tuesday'),
-                                            ('3', 'Wednesday'),
-                                            ('4', 'Thursday'),
-                                            ('5', 'Friday'),
-                                            ('6', 'Saturday'),
-                                            ('7', 'Sunday')],
-                                   default=list('1234567'),
-                                   validators=[validators.required()],
-                                   description='The alarm will ring only on '
-                                   'selected weekdays')
-    submit = SubmitField('Submit')
+    nick = StringField(
+        "Alarm nick",
+        validators=[validators.required()],
+        description="A simple name to recognize this alarm",
+    )
+    interval = StringField(
+        "Frequency",
+        validators=[validators.required()],
+        description="in seconds, or human-readable " "(like 9w3d12h)",
+    )
+    start = EasyDateTimeField(
+        "Start date and time",
+        validators=[validators.optional()],
+        description="Before this, no alarm will ring. "
+        "Expressed as YYYY-MM-DDTHH:MM:SS. If omitted, "
+        "the alarm will always ring",
+    )
+    end = EasyDateTimeField(
+        "End date and time",
+        validators=[validators.optional()],
+        description="After this, no alarm will ring. "
+        "Expressed as YYYY-MM-DDTHH:MM:SS. If omitted, "
+        "the alarm will always ring",
+    )
+    weekdays = SelectMultipleField(
+        "Days on which the alarm should be played",
+        choices=[
+            ("1", "Monday"),
+            ("2", "Tuesday"),
+            ("3", "Wednesday"),
+            ("4", "Thursday"),
+            ("5", "Friday"),
+            ("6", "Saturday"),
+            ("7", "Sunday"),
+        ],
+        default=list("1234567"),
+        validators=[validators.required()],
+        description="The alarm will ring only on " "selected weekdays",
+    )
+    submit = SubmitField("Submit")
 
 
     def populate_from_timespec(self, timespec):
     def populate_from_timespec(self, timespec):
-        if 'nick' in timespec:
-            self.nick.data = timespec['nick']
-        if 'start' in timespec:
-            self.start.data = datetime.fromtimestamp(timespec['start'])
-        if 'end' in timespec:
-            self.end.data = datetime.fromtimestamp(timespec['end'])
-        if 'weekdays' in timespec:
-            self.weekdays.data = timespec['weekdays']
+        if "nick" in timespec:
+            self.nick.data = timespec["nick"]
+        if "start" in timespec:
+            self.start.data = datetime.fromtimestamp(timespec["start"])
+        if "end" in timespec:
+            self.end.data = datetime.fromtimestamp(timespec["end"])
+        if "weekdays" in timespec:
+            self.weekdays.data = timespec["weekdays"]
         else:
         else:
-            self.weekdays.data = list('1234567')
-        self.interval.data = timespec['interval']
+            self.weekdays.data = list("1234567")
+        self.interval.data = timespec["interval"]
 
 
     def validate_interval(self, field):
     def validate_interval(self, field):
         try:
         try:
             int(field.data)
             int(field.data)
         except ValueError:
         except ValueError:
             if timeparse(field.data) is None:
             if timeparse(field.data) is None:
-                raise ValidationError("interval must either be a number "
-                                      "(in seconds) or a human-readable "
-                                      "string like '1h2m'  or '1d12h'")
+                raise ValidationError(
+                    "interval must either be a number "
+                    "(in seconds) or a human-readable "
+                    "string like '1h2m'  or '1d12h'"
+                )
 
 
 
 
 def frequencyalarm_receive(form):
 def frequencyalarm_receive(form):
     obj = {
     obj = {
-        'kind': 'frequency',
-        'nick': form.nick.data,
-        'interval': form.interval.data,
-        'weekdays': form.weekdays.data,
+        "kind": "frequency",
+        "nick": form.nick.data,
+        "interval": form.interval.data,
+        "weekdays": form.weekdays.data,
     }
     }
     if form.start.data:
     if form.start.data:
-        obj['start'] = int(form.start.data.strftime('%s'))
+        obj["start"] = int(form.start.data.strftime("%s"))
     else:
     else:
-        obj['start'] = 0
+        obj["start"] = 0
     if form.end.data:
     if form.end.data:
-        obj['end'] = int(form.end.data.strftime('%s'))
+        obj["end"] = int(form.end.data.strftime("%s"))
     return obj
     return obj

+ 36 - 19
larigira/timegen.py

@@ -1,6 +1,6 @@
-'''
+"""
 main module to read and get informations about alarms
 main module to read and get informations about alarms
-'''
+"""
 from __future__ import print_function
 from __future__ import print_function
 import sys
 import sys
 from datetime import datetime
 from datetime import datetime
@@ -8,31 +8,48 @@ import argparse
 import json
 import json
 from .entrypoints_utils import get_one_entrypoint
 from .entrypoints_utils import get_one_entrypoint
 from logging import getLogger
 from logging import getLogger
-log = getLogger('timegen')
+
+log = getLogger("timegen")
 
 
 
 
 def get_timegenerator(kind):
 def get_timegenerator(kind):
-    '''Messes with entrypoints to return an timegenerator function'''
-    return get_one_entrypoint('larigira.timegenerators', kind)
+    """Messes with entrypoints to return an timegenerator function"""
+    return get_one_entrypoint("larigira.timegenerators", kind)
 
 
 
 
 def get_parser():
 def get_parser():
     parser = argparse.ArgumentParser(
     parser = argparse.ArgumentParser(
-        description='Generate "ring times" from a timespec')
-    parser.add_argument('timespec', metavar='TIMESPEC', type=str, nargs=1,
-                        help='filename for timespec, formatted in json')
-    parser.add_argument('--now', metavar='NOW', type=int, nargs=1,
-                        default=None,
-                        help='Set a different "time", in unix epoch')
-    parser.add_argument('--howmany', metavar='N', type=int, nargs=1,
-                        default=[1],
-                        help='Set a different "time", in unix epoch')
+        description='Generate "ring times" from a timespec'
+    )
+    parser.add_argument(
+        "timespec",
+        metavar="TIMESPEC",
+        type=str,
+        nargs=1,
+        help="filename for timespec, formatted in json",
+    )
+    parser.add_argument(
+        "--now",
+        metavar="NOW",
+        type=int,
+        nargs=1,
+        default=None,
+        help='Set a different "time", in unix epoch',
+    )
+    parser.add_argument(
+        "--howmany",
+        metavar="N",
+        type=int,
+        nargs=1,
+        default=[1],
+        help='Set a different "time", in unix epoch',
+    )
     return parser
     return parser
 
 
 
 
 def read_spec(fname):
 def read_spec(fname):
     try:
     try:
-        if fname == '-':
+        if fname == "-":
             return json.load(sys.stdin)
             return json.load(sys.stdin)
         with open(fname) as buf:
         with open(fname) as buf:
             return json.load(buf)
             return json.load(buf)
@@ -42,12 +59,12 @@ def read_spec(fname):
 
 
 
 
 def check_spec(spec):
 def check_spec(spec):
-    if 'kind' not in spec:
+    if "kind" not in spec:
         yield "Missing field 'kind'"
         yield "Missing field 'kind'"
 
 
 
 
 def timegenerate(spec, now=None, howmany=1):
 def timegenerate(spec, now=None, howmany=1):
-    Alarm = get_timegenerator(spec['kind'])
+    Alarm = get_timegenerator(spec["kind"])
     generator = Alarm(spec)
     generator = Alarm(spec)
     if now is not None:
     if now is not None:
         if type(now) is not datetime:
         if type(now) is not datetime:
@@ -58,14 +75,14 @@ def timegenerate(spec, now=None, howmany=1):
 
 
 
 
 def main():
 def main():
-    '''Main function for the "larigira-timegen" executable'''
+    """Main function for the "larigira-timegen" executable"""
     args = get_parser().parse_args()
     args = get_parser().parse_args()
     spec = read_spec(args.timespec[0])
     spec = read_spec(args.timespec[0])
     errors = tuple(check_spec(spec))
     errors = tuple(check_spec(spec))
     if errors:
     if errors:
         log.error("Errors in timespec")
         log.error("Errors in timespec")
         for err in errors:
         for err in errors:
-            sys.stderr.write('Error: {}\n'.format(err))
+            sys.stderr.write("Error: {}\n".format(err))
         sys.exit(1)
         sys.exit(1)
     now = None if args.now is None else args.now.pop()
     now = None if args.now is None else args.now.pop()
     howmany = None if args.howmany is None else args.howmany.pop()
     howmany = None if args.howmany is None else args.howmany.pop()

+ 33 - 34
larigira/timegen_every.py

@@ -1,6 +1,7 @@
 from __future__ import print_function
 from __future__ import print_function
 import logging
 import logging
-log = logging.getLogger('time-every')
+
+log = logging.getLogger("time-every")
 from datetime import datetime, timedelta
 from datetime import datetime, timedelta
 
 
 from pytimeparse.timeparse import timeparse
 from pytimeparse.timeparse import timeparse
@@ -17,21 +18,21 @@ class Alarm(object):
         pass
         pass
 
 
     def next_ring(self, current_time=None):
     def next_ring(self, current_time=None):
-        '''if current_time is None, it is now()
+        """if current_time is None, it is now()
 
 
         returns the next time it will ring; or None if it will not anymore
         returns the next time it will ring; or None if it will not anymore
-        '''
+        """
         raise NotImplementedError()
         raise NotImplementedError()
 
 
     def has_ring(self, time=None):
     def has_ring(self, time=None):
-        '''returns True IFF the alarm will ring exactly at ``time``'''
+        """returns True IFF the alarm will ring exactly at ``time``"""
         raise NotImplementedError()
         raise NotImplementedError()
 
 
     def all_rings(self, current_time=None):
     def all_rings(self, current_time=None):
-        '''
+        """
         all future rings
         all future rings
         this, of course, is an iterator (they could be infinite)
         this, of course, is an iterator (they could be infinite)
-        '''
+        """
         ring = self.next_ring(current_time)
         ring = self.next_ring(current_time)
         while ring is not None:
         while ring is not None:
             yield ring
             yield ring
@@ -39,17 +40,18 @@ class Alarm(object):
 
 
 
 
 class SingleAlarm(Alarm):
 class SingleAlarm(Alarm):
-    '''
+    """
     rings a single time
     rings a single time
-    '''
-    description = 'Only once, at a specified date and time'
+    """
+
+    description = "Only once, at a specified date and time"
 
 
     def __init__(self, obj):
     def __init__(self, obj):
         super().__init__()
         super().__init__()
-        self.dt = getdate(obj['timestamp'])
+        self.dt = getdate(obj["timestamp"])
 
 
     def next_ring(self, current_time=None):
     def next_ring(self, current_time=None):
-        '''if current_time is None, it is now()'''
+        """if current_time is None, it is now()"""
         if current_time is None:
         if current_time is None:
             current_time = datetime.now()
             current_time = datetime.now()
         if current_time >= self.dt:
         if current_time >= self.dt:
@@ -63,29 +65,28 @@ class SingleAlarm(Alarm):
 
 
 
 
 class FrequencyAlarm(Alarm):
 class FrequencyAlarm(Alarm):
-    '''
+    """
     rings on {t | exists a k integer >= 0 s.t. t = start+k*t, start<t<end}
     rings on {t | exists a k integer >= 0 s.t. t = start+k*t, start<t<end}
-    '''
-    description = 'Events at a specified frequency. Example: every 30minutes'
+    """
+
+    description = "Events at a specified frequency. Example: every 30minutes"
 
 
     def __init__(self, obj):
     def __init__(self, obj):
-        self.start = getdate(obj['start'])
+        self.start = getdate(obj["start"])
         try:
         try:
-            self.interval = int(obj['interval'])
+            self.interval = int(obj["interval"])
         except ValueError:
         except ValueError:
-            self.interval = timeparse(obj['interval'])
+            self.interval = timeparse(obj["interval"])
         assert type(self.interval) is int
         assert type(self.interval) is int
-        self.end = getdate(obj['end']) if 'end' in obj else None
-        self.weekdays = [int(x) for x in obj['weekdays']] if \
-            'weekdays' in obj else None
+        self.end = getdate(obj["end"]) if "end" in obj else None
+        self.weekdays = [int(x) for x in obj["weekdays"]] if "weekdays" in obj else None
         if self.weekdays is not None:
         if self.weekdays is not None:
             for weekday in self.weekdays:
             for weekday in self.weekdays:
                 if not 1 <= weekday <= 7:
                 if not 1 <= weekday <= 7:
-                    raise ValueError('Not a valid weekday: {}'
-                                     .format(weekday))
+                    raise ValueError("Not a valid weekday: {}".format(weekday))
 
 
     def next_ring(self, current_time=None):
     def next_ring(self, current_time=None):
-        '''if current_time is None, it is now()'''
+        """if current_time is None, it is now()"""
         if current_time is None:
         if current_time is None:
             current_time = datetime.now()
             current_time = datetime.now()
         if self.end is not None and current_time > self.end:
         if self.end is not None and current_time > self.end:
@@ -100,22 +101,22 @@ class FrequencyAlarm(Alarm):
         # fact, it is necessary to retry until a valid event/weekday is
         # fact, it is necessary to retry until a valid event/weekday is
         # found. a "while True" might have been more elegant (and maybe
         # found. a "while True" might have been more elegant (and maybe
         # fast), but this gives a clear upper bound to the cycle.
         # fast), but this gives a clear upper bound to the cycle.
-        for _ in range(max(60*60*24*7 // self.interval, 1)):
+        for _ in range(max(60 * 60 * 24 * 7 // self.interval, 1)):
             n_interval = (
             n_interval = (
                 (current_time - self.start).total_seconds() // self.interval
                 (current_time - self.start).total_seconds() // self.interval
-                ) + 1
+            ) + 1
             ring = self.start + timedelta(seconds=self.interval * n_interval)
             ring = self.start + timedelta(seconds=self.interval * n_interval)
             if ring == current_time:
             if ring == current_time:
                 ring += timedelta(seconds=self.interval)
                 ring += timedelta(seconds=self.interval)
             if self.end is not None and ring > self.end:
             if self.end is not None and ring > self.end:
                 return None
                 return None
-            if self.weekdays is not None \
-               and ring.isoweekday() not in self.weekdays:
+            if self.weekdays is not None and ring.isoweekday() not in self.weekdays:
                 current_time = ring
                 current_time = ring
                 continue
                 continue
             return ring
             return ring
-        log.warning("Can't find a valid time for event %s; "
-                    "something went wrong", str(self))
+        log.warning(
+            "Can't find a valid time for event %s; " "something went wrong", str(self)
+        )
         return None
         return None
 
 
     def has_ring(self, current_time=None):
     def has_ring(self, current_time=None):
@@ -124,11 +125,9 @@ class FrequencyAlarm(Alarm):
         if not self.start >= current_time >= self.end:
         if not self.start >= current_time >= self.end:
             return False
             return False
 
 
-        n_interval = (current_time - self.start).total_seconds() // \
-            self.interval
-        expected_time = self.start + \
-            timedelta(seconds=self.interval * n_interval)
+        n_interval = (current_time - self.start).total_seconds() // self.interval
+        expected_time = self.start + timedelta(seconds=self.interval * n_interval)
         return expected_time == current_time
         return expected_time == current_time
 
 
     def __str__(self):
     def __str__(self):
-        return 'FrequencyAlarm(every %ds)' % self.interval
+        return "FrequencyAlarm(every %ds)" % self.interval

+ 25 - 20
larigira/unused.py

@@ -1,9 +1,9 @@
-'''
+"""
 This component will look for files to be removed. There are some assumptions:
 This component will look for files to be removed. There are some assumptions:
     * Only files in $TMPDIR are removed. Please remember that larigira has its
     * Only files in $TMPDIR are removed. Please remember that larigira has its
       own specific TMPDIR
       own specific TMPDIR
     * MPD URIs are parsed, and only file:/// is supported
     * MPD URIs are parsed, and only file:/// is supported
-'''
+"""
 import os
 import os
 from os.path import normpath
 from os.path import normpath
 import logging
 import logging
@@ -11,13 +11,14 @@ import mpd
 
 
 
 
 def old_commonpath(directories):
 def old_commonpath(directories):
-    if any(p for p in directories if p.startswith('/')) and \
-       any(p for p in directories if not p.startswith('/')):
+    if any(p for p in directories if p.startswith("/")) and any(
+        p for p in directories if not p.startswith("/")
+    ):
         raise ValueError("Can't mix absolute and relative paths")
         raise ValueError("Can't mix absolute and relative paths")
     norm_paths = [normpath(p) + os.path.sep for p in directories]
     norm_paths = [normpath(p) + os.path.sep for p in directories]
     ret = os.path.dirname(os.path.commonprefix(norm_paths))
     ret = os.path.dirname(os.path.commonprefix(norm_paths))
-    if len(ret) > 0 and ret == '/' * len(ret):
-        return '/'
+    if len(ret) > 0 and ret == "/" * len(ret):
+        return "/"
     return ret
     return ret
 
 
 
 
@@ -35,35 +36,39 @@ class UnusedCleaner:
 
 
     def _get_mpd(self):
     def _get_mpd(self):
         mpd_client = mpd.MPDClient(use_unicode=True)
         mpd_client = mpd.MPDClient(use_unicode=True)
-        mpd_client.connect(self.conf['MPD_HOST'], self.conf['MPD_PORT'])
+        mpd_client.connect(self.conf["MPD_HOST"], self.conf["MPD_PORT"])
         return mpd_client
         return mpd_client
 
 
     def watch(self, uri):
     def watch(self, uri):
-        '''
+        """
         adds fpath to the list of "watched" file
         adds fpath to the list of "watched" file
 
 
         as soon as it leaves the mpc playlist, it is removed
         as soon as it leaves the mpc playlist, it is removed
-        '''
-        if not uri.startswith('file:///'):
+        """
+        if not uri.startswith("file:///"):
             return  # not a file URI
             return  # not a file URI
-        fpath = uri[len('file://'):]
-        if 'TMPDIR' in self.conf and self.conf['TMPDIR'] \
-           and commonpath([self.conf['TMPDIR'], fpath]) != \
-           normpath(self.conf['TMPDIR']):
-            self.log.info('Not watching file %s: not in TMPDIR', fpath)
+        fpath = uri[len("file://") :]
+        if (
+            "TMPDIR" in self.conf
+            and self.conf["TMPDIR"]
+            and commonpath([self.conf["TMPDIR"], fpath])
+            != normpath(self.conf["TMPDIR"])
+        ):
+            self.log.info("Not watching file %s: not in TMPDIR", fpath)
             return
             return
         if not os.path.exists(fpath):
         if not os.path.exists(fpath):
-            self.log.warning('a path that does not exist is being monitored')
+            self.log.warning("a path that does not exist is being monitored")
         self.waiting_removal_files.add(fpath)
         self.waiting_removal_files.add(fpath)
 
 
     def check_playlist(self):
     def check_playlist(self):
-        '''check playlist + internal watchlist to see what can be removed'''
+        """check playlist + internal watchlist to see what can be removed"""
         mpdc = self._get_mpd()
         mpdc = self._get_mpd()
-        files_in_playlist = {song['file'] for song in mpdc.playlistid()
-                             if song['file'].startswith('/')}
+        files_in_playlist = {
+            song["file"] for song in mpdc.playlistid() if song["file"].startswith("/")
+        }
         for fpath in self.waiting_removal_files - files_in_playlist:
         for fpath in self.waiting_removal_files - files_in_playlist:
             # we can remove it!
             # we can remove it!
-            self.log.debug('removing unused: %s', fpath)
+            self.log.debug("removing unused: %s", fpath)
             self.waiting_removal_files.remove(fpath)
             self.waiting_removal_files.remove(fpath)
             if os.path.exists(fpath):
             if os.path.exists(fpath):
                 os.unlink(fpath)
                 os.unlink(fpath)