Skip to content

Commit

Permalink
Implement types; cleanup; part 1 (#829)
Browse files Browse the repository at this point in the history
  • Loading branch information
aw-was-here committed May 29, 2023
1 parent 1d372a6 commit 106532f
Show file tree
Hide file tree
Showing 76 changed files with 1,768 additions and 2,604 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@ nowplaying/version.py
NowPlaying.egg-info
nowplaying/qtrc.py
.DS_Store
nowplaying.sublime-project
nowplaying.sublime-workspace
3 changes: 0 additions & 3 deletions .yapfignore

This file was deleted.

3 changes: 1 addition & 2 deletions NowPlaying.spec
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,7 @@ for execname, execpy in executables.items():
binaries=[],
datas=[('nowplaying/resources/*', 'resources/'),
('nowplaying/templates/*', 'templates/')],
hiddenimports=ARTEXTRAS_MODULES + INPUT_MODULES +
RECOGNITION_MODULES,
hiddenimports=ARTEXTRAS_MODULES + INPUT_MODULES + RECOGNITION_MODULES,
hookspath=[('nowplaying/__pyinstaller')],
runtime_hooks=[],
excludes=[],
Expand Down
15 changes: 5 additions & 10 deletions conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import tempfile

import pytest
from PySide6.QtCore import QCoreApplication, QSettings, QStandardPaths # pylint: disable=no-name-in-module
from PySide6.QtCore import QCoreApplication, QSettings, QStandardPaths # pylint: disable=import-error, no-name-in-module

import nowplaying.bootstrap
import nowplaying.config
Expand Down Expand Up @@ -59,9 +59,7 @@ def bootstrap(getroot): # pylint: disable=redefined-outer-name
with tempfile.TemporaryDirectory() as newpath:
bundledir = pathlib.Path(getroot).joinpath('nowplaying')
nowplaying.bootstrap.set_qt_names(domain=DOMAIN, appname='testsuite')
config = nowplaying.config.ConfigFile(bundledir=bundledir,
logpath=newpath,
testmode=True)
config = nowplaying.config.ConfigFile(bundledir=bundledir, logpath=newpath, testmode=True)
config.cparser.setValue('acoustidmb/enabled', False)
config.cparser.sync()
yield config
Expand All @@ -81,20 +79,17 @@ def clear_old_testsuite():
qsettingsformat = QSettings.NativeFormat

nowplaying.bootstrap.set_qt_names(appname='testsuite')
config = QSettings(qsettingsformat, QSettings.SystemScope,
QCoreApplication.organizationName(),
config = QSettings(qsettingsformat, QSettings.SystemScope, QCoreApplication.organizationName(),
QCoreApplication.applicationName())
config.clear()
config.sync()

cachedir = pathlib.Path(
QStandardPaths.standardLocations(QStandardPaths.CacheLocation)[0])
cachedir = pathlib.Path(QStandardPaths.standardLocations(QStandardPaths.CacheLocation)[0])
if 'testsuite' in cachedir.name and cachedir.exists():
logging.info('Removing %s', cachedir)
shutil.rmtree(cachedir)

config = QSettings(qsettingsformat, QSettings.UserScope,
QCoreApplication.organizationName(),
config = QSettings(qsettingsformat, QSettings.UserScope, QCoreApplication.organizationName(),
QCoreApplication.applicationName())
config.clear()
config.sync()
Expand Down
6 changes: 2 additions & 4 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,6 @@ def get_release_data():

extlinks = {
'stabledownloadlink':
(f'{basedownload}/{stablerelease}/NowPlaying-{stablerelease}-%s.zip',
'%s'),
'rcdownloadlink':
(f'{basedownload}/{rcrelease}/NowPlaying-{rcrelease}-%s.zip', '%s')
(f'{basedownload}/{stablerelease}/NowPlaying-{stablerelease}-%s.zip', '%s'),
'rcdownloadlink': (f'{basedownload}/{rcrelease}/NowPlaying-{rcrelease}-%s.zip', '%s')
}
3 changes: 1 addition & 2 deletions nowplaying/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,7 @@ def actualmain(beam=False): # pragma: no cover
if not nowplaying.bootstrap.verify_python_version():
sys.exit(1)

config = nowplaying.config.ConfigFile(logpath=logpath,
bundledir=bundledir)
config = nowplaying.config.ConfigFile(logpath=logpath, bundledir=bundledir)
logging.getLogger().setLevel(config.loglevel)
logging.captureWarnings(True)
logging.debug('Using pidfile %s/%s', pid.piddir, pid.pidname)
Expand Down
59 changes: 14 additions & 45 deletions nowplaying/artistextras/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,71 +3,40 @@

import logging
import sys
import typing as t

from nowplaying.exceptions import PluginVerifyError
from nowplaying.plugin import WNPBasePlugin


class ArtistExtrasPlugin():
class ArtistExtrasPlugin(WNPBasePlugin):
''' base class of plugins '''

def __init__(self, config=None, qsettings=None):
self.available = True
self.plugintype = 'input'
self.config = config
self.qwidget = None
self.uihelp = None

if qsettings:
self.defaults(qsettings)
return

if not self.config:
logging.debug('Plugin was not called with config')

#### Settings UI methods

def defaults(self, qsettings):
''' (re-)set the default configuration values for this plugin '''
raise NotImplementedError

def connect_settingsui(self, qwidget, uihelp):
''' connect any UI elements such as buttons '''
raise NotImplementedError

def load_settingsui(self, qwidget):
''' load values from config and populate page '''
raise NotImplementedError

def verify_settingsui(self, qwidget): #pylint: disable=no-self-use
''' verify the values in the UI prior to saving '''
raise PluginVerifyError('Plugin did not implement verification.')

def save_settingsui(self, qwidget):
''' take the settings page and save it '''
raise NotImplementedError
super().__init__(config=config, qsettings=qsettings)
self.plugintype: str = 'artistextras'

#### Plug-in methods

def download(self, metadata=None, imagecache=None): #pylint: disable=no-self-use
def download(self, metadata: t.Optional[dict[str, t.Any]] = None, imagecache=None) -> dict: # pylint: disable=no-self-use,unused-argument
''' return metadata '''
raise NotImplementedError
return {}

def providerinfo(self):
def providerinfo(self) -> list: #pylint: disable=no-self-use, unused-argument
''' return list of what is provided by this recognition system '''
raise NotImplementedError
return []


#### Utilities

def calculate_delay(self):
def calculate_delay(self) -> float:
''' determine a reasonable, minimal delay '''

delay: float = 10.0

try:
delay = self.config.cparser.value('settings/delay',
type=float,
defaultValue=10.0)
delay = self.config.cparser.value('settings/delay', type=float, defaultValue=10.0)
except ValueError:
delay = 10.0
pass

if sys.platform == 'win32':
delay = max(delay / 2, 10)
Expand Down
116 changes: 55 additions & 61 deletions nowplaying/artistextras/discogs.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,32 +6,52 @@
import logging.handlers
import re
import socket
import typing as t

import requests.exceptions
import urllib3.exceptions
import nowplaying.vendor.discogs_client

import nowplaying.config
import nowplaying.vendor.discogs_client
from nowplaying.vendor.discogs_client import models
from nowplaying.artistextras import ArtistExtrasPlugin


class Plugin(ArtistExtrasPlugin):
''' handler for discogs '''

def __init__(self, config=None, qsettings=None):
def __init__(self, config=None, qsettings=None) -> None:
super().__init__(config=config, qsettings=qsettings)
self.displayname = "Discogs"
self.client = None
self.version = config.version
self.there = re.compile('(?i)^the ')
self.displayname: str = "Discogs"
self.client: t.Optional[nowplaying.vendor.discogs_client.Client] = None
self.there: re.Pattern = re.compile('(?i)^the ')

def _get_apikey(self):
apikey: str = self.config.cparser.value('discogs/apikey')
if not apikey or not self.config.cparser.value('discogs/enabled', type=bool):
return ''
return apikey

def _setup_client(self) -> bool:
''' setup the discogs client '''

if apikey := self._get_apikey():
delay = self.calculate_delay()
self.client = nowplaying.vendor.discogs_client.Client(
f'whatsnowplaying/{self.config.version}', user_token=apikey)
self.client.set_timeout(connect=delay, read=delay)
return True
return False

def _find_discogs_artist_releaselist(self, metadata: dict[str,
t.Any]) -> t.Optional[models.Artist]:
if not self.client and not self._setup_client():
return None

def _find_discogs_releaselist(self, metadata):
try:
logging.debug('Fetching %s - %s', metadata['artist'],
metadata['album'])
resultlist = self.client.search(metadata['album'],
artist=metadata['artist'],
type='title').page(1)
logging.debug('Fetching %s - %s', metadata['artist'], metadata['album'])
resultlist: list[models.Master] = self.client.search(metadata['album'],
artist=metadata['artist'],
type='title').page(1)
except (
requests.exceptions.ReadTimeout, # pragma: no cover
urllib3.exceptions.ReadTimeoutError,
Expand All @@ -44,45 +64,34 @@ def _find_discogs_releaselist(self, metadata):
return None

return next(
(result.artists[0] for result in resultlist if isinstance(
result, nowplaying.vendor.discogs_client.models.Release)),
(result.artists[0] for result in resultlist if isinstance(result, models.Release)),
None,
)

def download(self, metadata=None, imagecache=None): # pylint: disable=too-many-branches, too-many-return-statements
def download(self, metadata: t.Optional[dict] = None, imagecache=None) -> dict: # pylint: disable=too-many-branches, too-many-return-statements
''' download content '''

apikey = self.config.cparser.value('discogs/apikey')

if not apikey or not self.config.cparser.value('discogs/enabled',
type=bool):
return None

# discogs basically works by search for a combination of
# artist and album so we need both
if not metadata or not metadata.get('artist') or not metadata.get(
'album'):
if not metadata or not metadata.get('artist') or not metadata.get('album'):
logging.debug('artist or album is empty, skipping')
return None
return {}

oldartist = metadata['artist']

if not self.client:
delay = self.calculate_delay()
self.client = nowplaying.vendor.discogs_client.Client(
f'whatsnowplaying/{self.version}', user_token=apikey)
self.client.set_timeout(connect=delay, read=delay)
if not self.client and not self._setup_client():
logging.error('No discogs apikey or client setup failed.')
return {}

artistresultlist = self._find_discogs_releaselist(metadata)
oldartist: str = metadata['artist']
artistresultlist = self._find_discogs_artist_releaselist(metadata)

if not artistresultlist and self.there.match(metadata['artist']):
logging.debug('Trying without a leading \'The\'')
metadata['artist'] = self.there.sub('', metadata['artist'])
artistresultlist = self._find_discogs_releaselist(metadata)
artistresultlist = self._find_discogs_artist_releaselist(metadata)

if not artistresultlist:
logging.debug('discogs did not find it')
return None
return {}

if self.config.cparser.value('discogs/bio', type=bool):
metadata['artistlongbio'] = artistresultlist.profile_plaintext
Expand All @@ -100,56 +109,41 @@ def download(self, metadata=None, imagecache=None): # pylint: disable=too-many-
metadata['artistfanarturls'] = []

for record in artistresultlist.images:
if record['type'] == 'primary' and record.get(
'uri150') and self.config.cparser.value(
'discogs/thumbnails', type=bool):
if record['type'] == 'primary' and record.get('uri150') and self.config.cparser.value(
'discogs/thumbnails', type=bool):
imagecache.fill_queue(config=self.config,
artist=oldartist,
imagetype='artistthumb',
urllist=[record['uri150']])

if record['type'] == 'secondary' and record.get(
'uri') and self.config.cparser.value(
'discogs/fanart', type=bool
) and record['uri'] not in metadata['artistfanarturls']:
if record['type'] == 'secondary' and record.get('uri') and self.config.cparser.value(
'discogs/fanart',
type=bool) and record['uri'] not in metadata['artistfanarturls']:
metadata['artistfanarturls'].append(record['uri'])

return metadata

def providerinfo(self): # pylint: disable=no-self-use
def providerinfo(self) -> list: # pylint: disable=no-self-use
''' return list of what is provided by this plug-in '''
return [
'artistlongbio', 'artistthumbraw', 'discogs-artistfanarturls',
'artistwebsites'
]

def connect_settingsui(self, qwidget, uihelp):
''' pass '''
return ['artistlongbio', 'artistthumbraw', 'discogs-artistfanarturls', 'artistwebsites']

def load_settingsui(self, qwidget):
''' draw the plugin's settings page '''
if self.config.cparser.value('discogs/enabled', type=bool):
qwidget.discogs_checkbox.setChecked(True)
else:
qwidget.discogs_checkbox.setChecked(False)
qwidget.apikey_lineedit.setText(
self.config.cparser.value('discogs/apikey'))
qwidget.apikey_lineedit.setText(self.config.cparser.value('discogs/apikey'))

for field in ['bio', 'fanart', 'thumbnails', 'websites']:
func = getattr(qwidget, f'{field}_checkbox')
func.setChecked(
self.config.cparser.value(f'discogs/{field}', type=bool))

def verify_settingsui(self, qwidget):
''' pass '''
func.setChecked(self.config.cparser.value(f'discogs/{field}', type=bool))

def save_settingsui(self, qwidget):
''' take the settings page and save it '''

self.config.cparser.setValue('discogs/enabled',
qwidget.discogs_checkbox.isChecked())
self.config.cparser.setValue('discogs/apikey',
qwidget.apikey_lineedit.text())
self.config.cparser.setValue('discogs/enabled', qwidget.discogs_checkbox.isChecked())
self.config.cparser.setValue('discogs/apikey', qwidget.apikey_lineedit.text())

for field in ['bio', 'fanart', 'thumbnails', 'websites']:
func = getattr(qwidget, f'{field}_checkbox')
Expand Down
Loading

0 comments on commit 106532f

Please sign in to comment.