From a051d8c4d0a36fe038cff33c46f028af05093e0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Odd=20Str=C3=A5b=C3=B8?= Date: Tue, 6 Feb 2024 12:38:20 +0000 Subject: [PATCH 1/4] Remove .pyi files Change addon_id() function to constant ADDON_ID --- jellyfin_kodi/client.py | 6 +-- jellyfin_kodi/connect.py | 6 +-- jellyfin_kodi/database/jellyfin_db.py | 8 +++- jellyfin_kodi/dialogs/context.py | 4 +- jellyfin_kodi/helper/__init__.py | 2 +- jellyfin_kodi/helper/utils.py | 7 ++-- .../jellyfin_kodi/database/jellyfin_db.pyi | 39 ------------------- typings/jellyfin_kodi/helper/utils.pyi | 1 - 8 files changed, 18 insertions(+), 55 deletions(-) delete mode 100644 typings/jellyfin_kodi/database/jellyfin_db.pyi delete mode 100644 typings/jellyfin_kodi/helper/utils.pyi diff --git a/jellyfin_kodi/client.py b/jellyfin_kodi/client.py index 3aac72cbc..fb0002889 100644 --- a/jellyfin_kodi/client.py +++ b/jellyfin_kodi/client.py @@ -7,7 +7,7 @@ from kodi_six import xbmc, xbmcaddon, xbmcvfs -from .helper import translate, window, settings, addon_id, dialog, LazyLogger +from .helper import translate, window, settings, ADDON_ID, dialog, LazyLogger from .helper.utils import create_id, translate_path ################################################################################################## @@ -21,11 +21,11 @@ def get_addon_name(): ''' Used for logging. ''' - return xbmcaddon.Addon(addon_id()).getAddonInfo('name').upper() + return xbmcaddon.Addon(ADDON_ID).getAddonInfo('name').upper() def get_version(): - return xbmcaddon.Addon(addon_id()).getAddonInfo('version') + return xbmcaddon.Addon(ADDON_ID).getAddonInfo('version') def get_platform(): diff --git a/jellyfin_kodi/connect.py b/jellyfin_kodi/connect.py index 7566ce909..8ea0831db 100644 --- a/jellyfin_kodi/connect.py +++ b/jellyfin_kodi/connect.py @@ -8,7 +8,7 @@ from . import client from .database import get_credentials, save_credentials from .dialogs import ServerConnect, UsersConnect, LoginManual, ServerManual -from .helper import settings, addon_id, event, api, window, LazyLogger +from .helper import settings, ADDON_ID, event, api, window, LazyLogger from .jellyfin import Jellyfin from .jellyfin.connection_manager import CONNECTION_STATE from .helper.exceptions import HTTPException @@ -16,7 +16,7 @@ ################################################################################################## LOG = LazyLogger(__name__) -XML_PATH = (xbmcaddon.Addon(addon_id()).getAddonInfo('path'), "default", "1080i") +XML_PATH = (xbmcaddon.Addon(ADDON_ID).getAddonInfo('path'), "default", "1080i") ################################################################################################## @@ -129,7 +129,7 @@ def register_client(self, credentials=None, options=None, server_id=None, server except RuntimeError as error: LOG.exception(error) - xbmc.executebuiltin('Addon.OpenSettings(%s)' % addon_id()) + xbmc.executebuiltin('Addon.OpenSettings(%s)' % ADDON_ID) raise Exception('User sign in interrupted') diff --git a/jellyfin_kodi/database/jellyfin_db.py b/jellyfin_kodi/database/jellyfin_db.py index a518b830f..ce448656c 100644 --- a/jellyfin_kodi/database/jellyfin_db.py +++ b/jellyfin_kodi/database/jellyfin_db.py @@ -1,5 +1,8 @@ # -*- coding: utf-8 -*- from __future__ import division, absolute_import, print_function, unicode_literals + +from sqlite3 import Cursor + ################################################################################################# from . import queries as QU @@ -14,9 +17,10 @@ ################################################################################################## -class JellyfinDatabase(): +class JellyfinDatabase: + cursor: Cursor - def __init__(self, cursor): + def __init__(self, cursor: Cursor) -> None: self.cursor = cursor cursor.row_factory = sqlite_namedtuple_factory diff --git a/jellyfin_kodi/dialogs/context.py b/jellyfin_kodi/dialogs/context.py index 6d48a5034..0dec28c43 100644 --- a/jellyfin_kodi/dialogs/context.py +++ b/jellyfin_kodi/dialogs/context.py @@ -8,7 +8,7 @@ from kodi_six import xbmcgui, xbmcaddon from six import ensure_text -from ..helper import window, addon_id +from ..helper import window, ADDON_ID from ..helper import LazyLogger ################################################################################################## @@ -73,7 +73,7 @@ def onAction(self, action): def _add_editcontrol(self, x, y, height, width, password=0): - media = os.path.join(xbmcaddon.Addon(addon_id()).getAddonInfo('path'), 'resources', 'skins', 'default', 'media') + media = os.path.join(xbmcaddon.Addon(ADDON_ID).getAddonInfo('path'), 'resources', 'skins', 'default', 'media') control = xbmcgui.ControlImage(0, 0, 0, 0, filename=os.path.join(media, "white.png"), aspectRatio=0, diff --git a/jellyfin_kodi/helper/__init__.py b/jellyfin_kodi/helper/__init__.py index 4aa025744..3fd7164a1 100644 --- a/jellyfin_kodi/helper/__init__.py +++ b/jellyfin_kodi/helper/__init__.py @@ -4,7 +4,7 @@ from .translate import translate -from .utils import addon_id +from .utils import ADDON_ID from .utils import window from .utils import settings from .utils import kodi_version diff --git a/jellyfin_kodi/helper/utils.py b/jellyfin_kodi/helper/utils.py index 798e00aeb..aefb275d7 100644 --- a/jellyfin_kodi/helper/utils.py +++ b/jellyfin_kodi/helper/utils.py @@ -29,11 +29,10 @@ ################################################################################################# -def addon_id(): - return "plugin.video.jellyfin" +ADDON_ID = "plugin.video.jellyfin" -def kodi_version(): +def kodi_version() -> int: # Kodistubs returns empty string, causing Python 3 tests to choke on int() # TODO: Make Kodistubs version configurable for testing purposes if sys.version_info.major == 2: @@ -84,7 +83,7 @@ def settings(setting, value=None): ''' Get or add add-on settings. getSetting returns unicode object. ''' - addon = xbmcaddon.Addon(addon_id()) + addon = xbmcaddon.Addon(ADDON_ID) if value is not None: if setting.endswith('.bool'): diff --git a/typings/jellyfin_kodi/database/jellyfin_db.pyi b/typings/jellyfin_kodi/database/jellyfin_db.pyi deleted file mode 100644 index 2cfc2a3d6..000000000 --- a/typings/jellyfin_kodi/database/jellyfin_db.pyi +++ /dev/null @@ -1,39 +0,0 @@ -from sqlite3 import Cursor -from typing import Any, List, Optional, NamedTuple - - -class ViewRow(NamedTuple): - view_id: str - view_name: str - media_type: str - - -class JellyfinDatabase: - cursor: Cursor = ... - def __init__(self, cursor: Cursor) -> None: ... - def get_view(self, *args: Any) -> Optional[ViewRow]: ... - def get_views(self) -> List[ViewRow]: ... - - # def get_item_by_id(self, *args: Any): ... - # def add_reference(self, *args: Any) -> None: ... - # def update_reference(self, *args: Any) -> None: ... - # def update_parent_id(self, *args: Any) -> None: ... - # def get_item_id_by_parent_id(self, *args: Any): ... - # def get_item_by_parent_id(self, *args: Any): ... - # def get_item_by_media_folder(self, *args: Any): ... - # def get_item_by_wild_id(self, item_id: Any): ... - # def get_checksum(self, *args: Any): ... - # def get_item_by_kodi_id(self, *args: Any): ... - # def get_full_item_by_kodi_id(self, *args: Any): ... - # def get_media_by_id(self, *args: Any): ... - # def get_media_by_parent_id(self, *args: Any): ... - # def remove_item(self, *args: Any) -> None: ... - # def remove_items_by_parent_id(self, *args: Any) -> None: ... - # def remove_item_by_kodi_id(self, *args: Any) -> None: ... - # def remove_wild_item(self, item_id: Any) -> None: ... - # def get_view_name(self, item_id: Any): ... - # def add_view(self, *args: Any) -> None: ... - # def remove_view(self, *args: Any) -> None: ... - # def get_views_by_media(self, *args: Any): ... - # def get_items_by_media(self, *args: Any): ... - # def remove_media_by_parent_id(self, *args: Any) -> None: ... diff --git a/typings/jellyfin_kodi/helper/utils.pyi b/typings/jellyfin_kodi/helper/utils.pyi deleted file mode 100644 index 3629bba0b..000000000 --- a/typings/jellyfin_kodi/helper/utils.pyi +++ /dev/null @@ -1 +0,0 @@ -def kodi_version(self) -> int: ... From 10ea2887a2190a14922c58c7516c71af2d4bfc96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Odd=20Str=C3=A5b=C3=B8?= Date: Wed, 7 Feb 2024 08:28:01 +0000 Subject: [PATCH 2/4] Make mypy happy(er) --- build.py | 5 ++++- jellyfin_kodi/connect.py | 8 ++++---- jellyfin_kodi/dialogs/context.py | 3 ++- jellyfin_kodi/dialogs/serverconnect.py | 4 +++- jellyfin_kodi/full_sync.py | 3 ++- jellyfin_kodi/jellyfin/__init__.py | 13 +++++++------ jellyfin_kodi/jellyfin/connection_manager.py | 4 ++-- jellyfin_kodi/jellyfin/ws_client.py | 2 +- jellyfin_kodi/monitor.py | 3 ++- jellyfin_kodi/objects/obj.py | 3 ++- jellyfin_kodi/player.py | 3 ++- 11 files changed, 31 insertions(+), 20 deletions(-) diff --git a/build.py b/build.py index bf4495240..518f1225a 100755 --- a/build.py +++ b/build.py @@ -41,8 +41,11 @@ def create_addon_xml(config: dict, source: str, py_version: str) -> None: # Populate dependencies in template dependencies = config['dependencies'].get(py_version) + requires_el = root.find('requires') + assert isinstance(requires_el, ET.Element), "Unable to find requires element in template" + for dep in dependencies: - ET.SubElement(root.find('requires'), 'import', attrib=dep) + ET.SubElement(requires_el, 'import', attrib=dep) # Populate version string addon_version = config.get('version') diff --git a/jellyfin_kodi/connect.py b/jellyfin_kodi/connect.py index 8ea0831db..79226632b 100644 --- a/jellyfin_kodi/connect.py +++ b/jellyfin_kodi/connect.py @@ -144,12 +144,12 @@ def get_user(self, client): ''' Save user info. ''' - self.user = client.jellyfin.get_user() - settings('username', self.user['Name']) + user = client.jellyfin.get_user() + settings('username', user['Name']) - if 'PrimaryImageTag' in self.user: + if 'PrimaryImageTag' in user: server_address = client.auth.get_server_info(client.auth.server_id)['address'] - window('JellyfinUserImage', api.API(self.user, server_address).get_user_artwork(self.user['Id'])) + window('JellyfinUserImage', api.API(user, server_address).get_user_artwork(user['Id'])) def select_servers(self, state=None): diff --git a/jellyfin_kodi/dialogs/context.py b/jellyfin_kodi/dialogs/context.py index 0dec28c43..243d749b8 100644 --- a/jellyfin_kodi/dialogs/context.py +++ b/jellyfin_kodi/dialogs/context.py @@ -4,6 +4,7 @@ ################################################################################################## import os +from typing import List from kodi_six import xbmcgui, xbmcaddon from six import ensure_text @@ -27,7 +28,7 @@ class ContextMenu(xbmcgui.WindowXMLDialog): - _options = [] + _options: List[str] = [] selected_option = None def __init__(self, *args, **kwargs): diff --git a/jellyfin_kodi/dialogs/serverconnect.py b/jellyfin_kodi/dialogs/serverconnect.py index 73d720587..f3c80a1b4 100644 --- a/jellyfin_kodi/dialogs/serverconnect.py +++ b/jellyfin_kodi/dialogs/serverconnect.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- from __future__ import division, absolute_import, print_function, unicode_literals +from typing import Any, Dict, List + ################################################################################################## from six import iteritems @@ -32,7 +34,7 @@ class ServerConnect(xbmcgui.WindowXMLDialog): user_image = None - servers = [] + servers: List[Dict[str, Any]] = [] _selected_server = None _connect_login = False diff --git a/jellyfin_kodi/full_sync.py b/jellyfin_kodi/full_sync.py index 7463c3007..090624c51 100644 --- a/jellyfin_kodi/full_sync.py +++ b/jellyfin_kodi/full_sync.py @@ -5,6 +5,7 @@ from contextlib import contextmanager import datetime +from typing import Any, Dict from kodi_six import xbmc @@ -29,7 +30,7 @@ class FullSync(object): sync.libraries() ''' # Borg - multiple instances, shared state - _shared_state = {} + _shared_state: Dict[str, Any] = {} sync = None running = False screensaver = None diff --git a/jellyfin_kodi/jellyfin/__init__.py b/jellyfin_kodi/jellyfin/__init__.py index 6d0bcdec8..6c5b75a4e 100644 --- a/jellyfin_kodi/jellyfin/__init__.py +++ b/jellyfin_kodi/jellyfin/__init__.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- from __future__ import division, absolute_import, print_function, unicode_literals +from typing import Any, Dict + ################################################################################################# from ..helper import has_attribute, LazyLogger @@ -43,16 +45,15 @@ class Jellyfin(object): ''' # Borg - multiple instances, shared state - _shared_state = {} - client = {} - server_id = "default" + _shared_state: Dict[str, Any] = {} + client: Dict[str, JellyfinClient] = {} + server_id: str = "default" def __init__(self, server_id=None): self.__dict__ = self._shared_state self.server_id = server_id or "default" - def get_client(self): - # type: () -> JellyfinClient + def get_client(self) -> JellyfinClient: return self.client[self.server_id] def close(self): @@ -71,7 +72,7 @@ def close_all(cls): for client in cls.client: cls.client[client].stop() - cls.client = {} + cls.client.clear() LOG.info("---[ STOPPED ALL JELLYFINCLIENTS ]---") @classmethod diff --git a/jellyfin_kodi/jellyfin/connection_manager.py b/jellyfin_kodi/jellyfin/connection_manager.py index 9e562a636..125e643d0 100644 --- a/jellyfin_kodi/jellyfin/connection_manager.py +++ b/jellyfin_kodi/jellyfin/connection_manager.py @@ -8,6 +8,7 @@ from datetime import datetime from operator import itemgetter import traceback +from typing import Optional import urllib3 @@ -31,8 +32,7 @@ class ConnectionManager(object): - user = {} - server_id = None + server_id: Optional[str] = None def __init__(self, client): diff --git a/jellyfin_kodi/jellyfin/ws_client.py b/jellyfin_kodi/jellyfin/ws_client.py index 3a9ce122e..279e7c879 100644 --- a/jellyfin_kodi/jellyfin/ws_client.py +++ b/jellyfin_kodi/jellyfin/ws_client.py @@ -14,7 +14,7 @@ # If numpy is installed, the websockets library tries to use it, and then # kodi hard crashes for reasons I don't even want to pretend to understand import sys # noqa: E402,I100 -sys.modules['numpy'] = None +sys.modules['numpy'] = None # type: ignore [assignment] import websocket # noqa: E402,I201 ################################################################################################## diff --git a/jellyfin_kodi/monitor.py b/jellyfin_kodi/monitor.py index 31b9d67cd..b2d2bc8df 100644 --- a/jellyfin_kodi/monitor.py +++ b/jellyfin_kodi/monitor.py @@ -6,6 +6,7 @@ import binascii import json import threading +from typing import List from kodi_six import xbmc @@ -27,7 +28,7 @@ class Monitor(xbmc.Monitor): - servers = [] + servers: List[str] = [] sleep = False def __init__(self): diff --git a/jellyfin_kodi/objects/obj.py b/jellyfin_kodi/objects/obj.py index f9015791f..4e3f47189 100644 --- a/jellyfin_kodi/objects/obj.py +++ b/jellyfin_kodi/objects/obj.py @@ -5,6 +5,7 @@ import json import os +from typing import Any, Dict from six import iteritems, ensure_text @@ -20,7 +21,7 @@ class Objects(object): # Borg - multiple instances, shared state - _shared_state = {} + _shared_state: Dict[str, Any] = {} def __init__(self): diff --git a/jellyfin_kodi/player.py b/jellyfin_kodi/player.py index e8a92985a..64cf8993c 100644 --- a/jellyfin_kodi/player.py +++ b/jellyfin_kodi/player.py @@ -4,6 +4,7 @@ ################################################################################################# import os +from typing import Dict from kodi_six import xbmc, xbmcvfs @@ -22,7 +23,7 @@ class Player(xbmc.Player): - played = {} + played: Dict[str, dict] = {} up_next = False def __init__(self): From a190826ad15c302a315b655980f0af5a0a00c81c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Odd=20Str=C3=A5b=C3=B8?= Date: Wed, 7 Feb 2024 09:01:52 +0000 Subject: [PATCH 3/4] Fix lints --- build.py | 1 - jellyfin_kodi/entrypoint/default.py | 2 +- jellyfin_kodi/helper/translate.py | 2 +- jellyfin_kodi/helper/wrapper.py | 4 ++-- jellyfin_kodi/helper/xmls.py | 1 + jellyfin_kodi/monitor.py | 1 - jellyfin_kodi/objects/kodi/queries.py | 2 +- jellyfin_kodi/objects/obj.py | 2 +- jellyfin_kodi/views.py | 6 +++--- tox.ini | 2 +- 10 files changed, 11 insertions(+), 12 deletions(-) diff --git a/build.py b/build.py index 518f1225a..d0dfd4c84 100755 --- a/build.py +++ b/build.py @@ -113,7 +113,6 @@ def folder_filter(folder_name: str) -> bool: return True - if __name__ == '__main__': parser = argparse.ArgumentParser(description='Build flags:') parser.add_argument( diff --git a/jellyfin_kodi/entrypoint/default.py b/jellyfin_kodi/entrypoint/default.py index d8f6c049a..fe7cecb97 100644 --- a/jellyfin_kodi/entrypoint/default.py +++ b/jellyfin_kodi/entrypoint/default.py @@ -345,7 +345,7 @@ def browse(media, view_id=None, folder=None, server_id=None, api_client=None): actions = Actions(server_id, api_client) list_li = [] - listing = listing if type(listing) == list else listing.get('Items', []) + listing = listing if isinstance(listing, list) else listing.get('Items', []) for item in listing: diff --git a/jellyfin_kodi/helper/translate.py b/jellyfin_kodi/helper/translate.py index 2714e9ee3..04d6b9caa 100644 --- a/jellyfin_kodi/helper/translate.py +++ b/jellyfin_kodi/helper/translate.py @@ -18,7 +18,7 @@ def translate(string): ''' Get add-on string. Returns in unicode. ''' - if type(string) != int: + if not isinstance(string, int): string = STRINGS[string] result = xbmcaddon.Addon('plugin.video.jellyfin').getLocalizedString(string) diff --git a/jellyfin_kodi/helper/wrapper.py b/jellyfin_kodi/helper/wrapper.py index 1b814da19..294be59c6 100644 --- a/jellyfin_kodi/helper/wrapper.py +++ b/jellyfin_kodi/helper/wrapper.py @@ -27,7 +27,7 @@ def wrapper(self, item=None, *args, **kwargs): dialog = xbmcgui.DialogProgressBG() - if item and type(item) == dict: + if item and isinstance(item, dict): dialog.create(translate('addon_name'), "%s %s" % (translate('gathering'), item['Name'])) LOG.info("Processing %s: %s", item['Name'], item['Id']) @@ -72,7 +72,7 @@ def jellyfin_item(func): ''' Wrapper to retrieve the jellyfin_db item. ''' def wrapper(self, item, *args, **kwargs): - e_item = self.jellyfin_db.get_item_by_id(item['Id'] if type(item) == dict else item) + e_item = self.jellyfin_db.get_item_by_id(item['Id'] if isinstance(item, dict) else item) return func(self, item, e_item=e_item, *args, **kwargs) diff --git a/jellyfin_kodi/helper/xmls.py b/jellyfin_kodi/helper/xmls.py index 0831514b5..0e107091b 100644 --- a/jellyfin_kodi/helper/xmls.py +++ b/jellyfin_kodi/helper/xmls.py @@ -72,6 +72,7 @@ def advanced_settings(): return True + def verify_kodi_defaults(): ''' Make sure we have the kodi default folder in place. ''' diff --git a/jellyfin_kodi/monitor.py b/jellyfin_kodi/monitor.py index b2d2bc8df..fd463bd6e 100644 --- a/jellyfin_kodi/monitor.py +++ b/jellyfin_kodi/monitor.py @@ -179,7 +179,6 @@ def server_instance(self, server_id=None): self.additional_users(server) - def additional_users(self, server): ''' Setup additional users images. diff --git a/jellyfin_kodi/objects/kodi/queries.py b/jellyfin_kodi/objects/kodi/queries.py index 71459e2c5..e966683ac 100644 --- a/jellyfin_kodi/objects/kodi/queries.py +++ b/jellyfin_kodi/objects/kodi/queries.py @@ -399,7 +399,7 @@ # Resulting in duplicates insert_link_if_not_exists = """ INSERT INTO {LinkType}(actor_id, media_id, media_type) -SELECT ?, ?, ? +SELECT ?, ?, ? WHERE NOT EXISTS(SELECT 1 FROM {LinkType} WHERE actor_id = ? AND media_id = ? AND media_type = ?) """ update_movie = """ diff --git a/jellyfin_kodi/objects/obj.py b/jellyfin_kodi/objects/obj.py index 4e3f47189..f42eb3c55 100644 --- a/jellyfin_kodi/objects/obj.py +++ b/jellyfin_kodi/objects/obj.py @@ -108,7 +108,7 @@ def map(self, item, mapping_name): continue if obj_key: - obj = [d[obj_key] for d in obj if d.get(obj_key)] if type(obj) == list else obj.get(obj_key) + obj = [d[obj_key] for d in obj if d.get(obj_key)] if isinstance(obj, list) else obj.get(obj_key) self.mapped_item[key] = obj break diff --git a/jellyfin_kodi/views.py b/jellyfin_kodi/views.py index 8569cb1ed..9c38ca451 100644 --- a/jellyfin_kodi/views.py +++ b/jellyfin_kodi/views.py @@ -411,7 +411,7 @@ def add_node(self, index, file, view, node, name): etree.SubElement(xml, 'content') label = xml.find('label') - label.text = str(name) if type(name) == int else name + label.text = str(name) if isinstance(name, int) else name content = xml.find('content') content.text = view['Media'] @@ -772,7 +772,7 @@ def window_node(self, index, view, node=None, node_label=None): else: window_path = "ActivateWindow(Videos,%s,return)" % path - node_label = translate(node_label) if type(node_label) == int else node_label + node_label = translate(node_label) if isinstance(node_label, int) else node_label node_label = node_label or view['Name'] if node in ('all', 'music'): @@ -824,7 +824,7 @@ def window_wnode(self, index, view, node=None, node_label=None): else: window_path = "ActivateWindow(Videos,%s,return)" % path - node_label = translate(node_label) if type(node_label) == int else node_label + node_label = translate(node_label) if isinstance(node_label, int) else node_label node_label = node_label or view['Name'] if node == 'all': diff --git a/tox.ini b/tox.ini index e8ea3d1cb..01f621549 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,7 @@ [flake8] max-line-length = 9999 import-order-style = pep8 -exclude = .git,.vscode,libraries,build.py,.github +exclude = .git,.vscode,libraries,.github extend-ignore = I202 per-file-ignores = From 49f6546aca806bf3f03f6f1b7dacb393928aa8f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Odd=20Str=C3=A5b=C3=B8?= Date: Thu, 8 Feb 2024 15:49:49 +0000 Subject: [PATCH 4/4] Make mypy happier --- jellyfin_kodi/connect.py | 2 +- jellyfin_kodi/database/__init__.py | 7 ++++--- jellyfin_kodi/dialogs/context.py | 4 ++-- jellyfin_kodi/dialogs/resume.py | 4 +++- jellyfin_kodi/entrypoint/default.py | 2 +- jellyfin_kodi/entrypoint/service.py | 20 ++++++++++-------- jellyfin_kodi/full_sync.py | 2 +- jellyfin_kodi/helper/api.py | 4 +++- jellyfin_kodi/helper/loghandler.py | 3 ++- jellyfin_kodi/helper/utils.py | 10 ++++----- jellyfin_kodi/helper/xmls.py | 2 +- jellyfin_kodi/jellyfin/utils.py | 2 +- jellyfin_kodi/library.py | 31 ++++++++++++++++------------ jellyfin_kodi/monitor.py | 14 ++++++------- jellyfin_kodi/objects/kodi/kodi.py | 6 +++++- jellyfin_kodi/objects/music.py | 3 ++- jellyfin_kodi/objects/musicvideos.py | 3 ++- jellyfin_kodi/objects/tvshows.py | 5 +++-- mypy.ini | 4 ++++ 19 files changed, 76 insertions(+), 52 deletions(-) create mode 100644 mypy.ini diff --git a/jellyfin_kodi/connect.py b/jellyfin_kodi/connect.py index 79226632b..7e69ec236 100644 --- a/jellyfin_kodi/connect.py +++ b/jellyfin_kodi/connect.py @@ -154,7 +154,7 @@ def get_user(self, client): def select_servers(self, state=None): state = state or self.connect_manager.connect({'enableAutoLogin': False}) - user = {} + user = {} # TODO: Fixme: content of this dict is used below, but can never contain anything dialog = ServerConnect("script-jellyfin-connect-server.xml", *XML_PATH) dialog.set_args( diff --git a/jellyfin_kodi/database/__init__.py b/jellyfin_kodi/database/__init__.py index af1bf859f..4ee5721a2 100644 --- a/jellyfin_kodi/database/__init__.py +++ b/jellyfin_kodi/database/__init__.py @@ -8,6 +8,7 @@ import sqlite3 import sys import re +from typing import Any, Dict from kodi_six import xbmc, xbmcvfs from six import text_type @@ -36,7 +37,7 @@ class Database(object): ''' timeout = 120 discovered = False - discovered_file = None + discovered_file: str = None def __init__(self, db_file=None, commit_close=True): @@ -318,7 +319,7 @@ def reset_artwork(): LOG.info("[ reset artwork ]") -def get_sync(): +def get_sync() -> dict: if (3, 0) <= sys.version_info < (3, 6): LOG.error("Python versions 3.0-3.5 are NOT supported.") @@ -342,7 +343,7 @@ def get_sync(): return sync -def save_sync(sync): +def save_sync(sync: Dict[str, Any]): if not xbmcvfs.exists(ADDON_DATA): xbmcvfs.mkdirs(ADDON_DATA) diff --git a/jellyfin_kodi/dialogs/context.py b/jellyfin_kodi/dialogs/context.py index 243d749b8..903d52027 100644 --- a/jellyfin_kodi/dialogs/context.py +++ b/jellyfin_kodi/dialogs/context.py @@ -4,7 +4,7 @@ ################################################################################################## import os -from typing import List +from typing import List, cast from kodi_six import xbmcgui, xbmcaddon from six import ensure_text @@ -52,7 +52,7 @@ def onInit(self): self.getControl(USER_IMAGE).setImage(window('JellyfinUserImage')) LOG.info("options: %s", self._options) - self.list_ = self.getControl(LIST) + self.list_ = cast(xbmcgui.ControlList, self.getControl(LIST)) for option in self._options: self.list_.addItem(self._add_listitem(option)) diff --git a/jellyfin_kodi/dialogs/resume.py b/jellyfin_kodi/dialogs/resume.py index 0357a413f..a161bc21b 100644 --- a/jellyfin_kodi/dialogs/resume.py +++ b/jellyfin_kodi/dialogs/resume.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- from __future__ import division, absolute_import, print_function, unicode_literals +from typing import Optional + ################################################################################################## from kodi_six import xbmc, xbmcgui @@ -22,7 +24,7 @@ class ResumeDialog(xbmcgui.WindowXMLDialog): _resume_point = None - selected_option = None + selected_option: Optional[int] = None def __init__(self, *args, **kwargs): xbmcgui.WindowXMLDialog.__init__(self, *args, **kwargs) diff --git a/jellyfin_kodi/entrypoint/default.py b/jellyfin_kodi/entrypoint/default.py index fe7cecb97..31504a6aa 100644 --- a/jellyfin_kodi/entrypoint/default.py +++ b/jellyfin_kodi/entrypoint/default.py @@ -885,7 +885,7 @@ def backup(): if xbmcvfs.exists(backup + '/'): if not dialog("yesno", "{jellyfin}", translate(33090)): - return backup() + return backup delete_folder(backup) diff --git a/jellyfin_kodi/entrypoint/service.py b/jellyfin_kodi/entrypoint/service.py index 8c3b6affe..9a3a1e704 100644 --- a/jellyfin_kodi/entrypoint/service.py +++ b/jellyfin_kodi/entrypoint/service.py @@ -6,6 +6,7 @@ import json import sys from datetime import datetime +from typing import Any, Dict # Workaround for threads using datetime: _striptime is locked import _strptime # noqa:F401 @@ -37,7 +38,7 @@ class Service(xbmc.Monitor): monitor = None play_event = None warn = True - settings = {'last_progress': datetime.today(), 'last_progress_report': datetime.today()} + settings: Dict[str, Any] = {'last_progress': datetime.today(), 'last_progress_report': datetime.today()} def __init__(self): @@ -264,17 +265,17 @@ def onNotification(self, sender, method, data): window('jellyfin_should_stop.bool', True) self.running = False - elif method in ('SyncLibrarySelection', 'RepairLibrarySelection', 'AddLibrarySelection', 'RemoveLibrarySelection'): + elif self.library_thread is not None and method in ('SyncLibrarySelection', 'RepairLibrarySelection', 'AddLibrarySelection', 'RemoveLibrarySelection'): self.library_thread.select_libraries(method) - elif method == 'SyncLibrary': + elif self.library_thread is not None and method == 'SyncLibrary': if not data.get('Id'): return self.library_thread.add_library(data['Id'], data.get('Update', False)) xbmc.executebuiltin("Container.Refresh") - elif method == 'RepairLibrary': + elif self.library_thread is not None and method == 'RepairLibrary': if not data.get('Id'): return @@ -288,7 +289,7 @@ def onNotification(self, sender, method, data): self.library_thread.add_library(data['Id']) xbmc.executebuiltin("Container.Refresh") - elif method == 'RemoveLibrary': + elif self.library_thread is not None and method == 'RemoveLibrary': libraries = data['Id'].split(',') for lib in libraries: @@ -309,10 +310,11 @@ def onNotification(self, sender, method, data): self.library_thread = None Jellyfin.close_all() - self.monitor.server = [] - self.monitor.sleep = True + if self.monitor is not None: + self.monitor.server = [] + self.monitor.sleep = True - elif method == 'System.OnWake': + elif self.monitor is not None and method == 'System.OnWake': if not self.monitor.sleep: LOG.warning("System.OnSleep was never called, skip System.OnWake") @@ -365,7 +367,7 @@ def onSettingsChanged(self): self.settings['enable_context_transcode'] = settings('enableContextTranscode.bool') LOG.info("New context transcode setting: %s", self.settings['enable_context_transcode']) - if settings('useDirectPaths') != self.settings['mode'] and self.library_thread.started: + if self.library_thread is not None and settings('useDirectPaths') != self.settings['mode'] and self.library_thread.started: self.settings['mode'] = settings('useDirectPaths') LOG.info("New playback mode setting: %s", self.settings['mode']) diff --git a/jellyfin_kodi/full_sync.py b/jellyfin_kodi/full_sync.py index 090624c51..2a85183e1 100644 --- a/jellyfin_kodi/full_sync.py +++ b/jellyfin_kodi/full_sync.py @@ -31,7 +31,7 @@ class FullSync(object): ''' # Borg - multiple instances, shared state _shared_state: Dict[str, Any] = {} - sync = None + sync = get_sync() running = False screensaver = None diff --git a/jellyfin_kodi/helper/api.py b/jellyfin_kodi/helper/api.py index ea8e9ee96..f01fc6e1a 100644 --- a/jellyfin_kodi/helper/api.py +++ b/jellyfin_kodi/helper/api.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- from __future__ import division, absolute_import, print_function, unicode_literals +from typing import Any, Dict, List + ################################################################################################## from . import settings, LazyLogger @@ -42,7 +44,7 @@ def get_naming(self): return self.item['Name'] def get_actors(self): - cast = [] + cast: List[Dict[str, Any]] = [] if 'People' in self.item: self.get_people_artwork(self.item['People']) diff --git a/jellyfin_kodi/helper/loghandler.py b/jellyfin_kodi/helper/loghandler.py index ce06ecb12..61ce45962 100644 --- a/jellyfin_kodi/helper/loghandler.py +++ b/jellyfin_kodi/helper/loghandler.py @@ -6,6 +6,7 @@ import os import logging import traceback +from typing import Dict, List from six import ensure_text from kodi_six import xbmc, xbmcaddon @@ -36,7 +37,7 @@ def __init__(self): logging.StreamHandler.__init__(self) self.setFormatter(MyFormatter()) - self.sensitive = {'Token': [], 'Server': []} + self.sensitive: Dict[str, List[str]] = {'Token': [], 'Server': []} for server in database.get_credentials()['Servers']: diff --git a/jellyfin_kodi/helper/utils.py b/jellyfin_kodi/helper/utils.py index aefb275d7..9c3ac0480 100644 --- a/jellyfin_kodi/helper/utils.py +++ b/jellyfin_kodi/helper/utils.py @@ -153,7 +153,8 @@ def event(method, data=None, sender=None, hexlify=False): xbmc.executebuiltin('NotifyAll(%s, %s, %s)' % (sender, method, data)) -def dialog(dialog_type, *args, **kwargs): +def dialog(dialog_type: str, *args: str, **kwargs): + arg_list = list(args) d = xbmcgui.Dialog() @@ -165,9 +166,8 @@ def dialog(dialog_type, *args, **kwargs): if "heading" in kwargs: kwargs['heading'] = kwargs['heading'].replace("{jellyfin}", translate('addon_name')) - if args: - args = list(args) - args[0] = args[0].replace("{jellyfin}", translate('addon_name')) + if arg_list: + arg_list[0] = arg_list[0].replace("{jellyfin}", translate('addon_name')) types = { 'yesno': d.yesno, @@ -178,7 +178,7 @@ def dialog(dialog_type, *args, **kwargs): 'numeric': d.numeric, 'multi': d.multiselect } - return types[dialog_type](*args, **kwargs) + return types[dialog_type](*arg_list, **kwargs) def should_stop(): diff --git a/jellyfin_kodi/helper/xmls.py b/jellyfin_kodi/helper/xmls.py index 0e107091b..32c6de6bb 100644 --- a/jellyfin_kodi/helper/xmls.py +++ b/jellyfin_kodi/helper/xmls.py @@ -27,7 +27,7 @@ def tvtunes_nfo(path, urls): except Exception: xml = etree.Element('tvtunes') - for elem in xml.getiterator('tvtunes'): + for elem in xml.iter('tvtunes'): for file in list(elem): elem.remove(file) diff --git a/jellyfin_kodi/jellyfin/utils.py b/jellyfin_kodi/jellyfin/utils.py index af5cab59e..5032b319d 100644 --- a/jellyfin_kodi/jellyfin/utils.py +++ b/jellyfin_kodi/jellyfin/utils.py @@ -33,7 +33,7 @@ def clean_none_dict_values(obj): if mutable: # Remove keys with None value for key in remove: - item.pop(key) + item.pop(key) # typing: ignore [attr-defined] elif isinstance(item, collections_abc.Iterable): for value in item: diff --git a/jellyfin_kodi/library.py b/jellyfin_kodi/library.py index 9de23d67e..d17b58dff 100644 --- a/jellyfin_kodi/library.py +++ b/jellyfin_kodi/library.py @@ -5,6 +5,7 @@ import threading from datetime import datetime, timedelta +from typing import Dict, List, Tuple from six.moves import queue as Queue @@ -47,18 +48,18 @@ def __init__(self, monitor): self.monitor = monitor self.player = monitor.monitor.player self.server = Jellyfin().get_client() - self.updated_queue = Queue.Queue() - self.userdata_queue = Queue.Queue() - self.removed_queue = Queue.Queue() + self.updated_queue: Queue.Queue[str] = Queue.Queue() + self.userdata_queue: Queue.Queue[str] = Queue.Queue() + self.removed_queue: Queue.Queue[str] = Queue.Queue() self.updated_output = self.__new_queues__() self.userdata_output = self.__new_queues__() self.removed_output = self.__new_queues__() - self.notify_output = Queue.Queue() + self.notify_output: Queue.Queue[Tuple[str, str]] = Queue.Queue() self.jellyfin_threads = [] self.download_threads = [] self.notify_threads = [] - self.writer_threads = {'updated': [], 'userdata': [], 'removed': []} + self.writer_threads: Dict[str, List[BaseWorker]] = {'updated': [], 'userdata': [], 'removed': []} self.database_lock = threading.Lock() self.music_database_lock = threading.Lock() @@ -222,10 +223,10 @@ def worker_downloads(self): ''' Get items from jellyfin and place them in the appropriate queues. ''' - for queue in ((self.updated_queue, self.updated_output), (self.userdata_queue, self.userdata_output)): - if queue[0].qsize() and len(self.download_threads) < DTHREADS: + for input_queue, output_queue in ((self.updated_queue, self.updated_output), (self.userdata_queue, self.userdata_output)): + if input_queue.qsize() and len(self.download_threads) < DTHREADS: - new_thread = GetItemWorker(self.server, queue[0], queue[1]) + new_thread = GetItemWorker(self.server, input_queue, output_queue) new_thread.start() LOG.info("-->[ q:download/%s ]", id(new_thread)) self.download_threads.append(new_thread) @@ -591,7 +592,11 @@ def removed(self, data): LOG.info("---[ removed:%s ]", len(data)) -class UpdateWorker(threading.Thread): +class BaseWorker(threading.Thread): + is_done: bool + + +class UpdateWorker(BaseWorker): is_done = False @@ -667,7 +672,7 @@ def run(self): self.is_done = True -class UserDataWorker(threading.Thread): +class UserDataWorker(BaseWorker): is_done = False @@ -730,7 +735,7 @@ def run(self): self.is_done = True -class SortWorker(threading.Thread): +class SortWorker(BaseWorker): is_done = False @@ -777,7 +782,7 @@ def run(self): self.is_done = True -class RemovedWorker(threading.Thread): +class RemovedWorker(BaseWorker): is_done = False @@ -838,7 +843,7 @@ def run(self): self.is_done = True -class NotifyWorker(threading.Thread): +class NotifyWorker(BaseWorker): is_done = False diff --git a/jellyfin_kodi/monitor.py b/jellyfin_kodi/monitor.py index fd463bd6e..3f4729392 100644 --- a/jellyfin_kodi/monitor.py +++ b/jellyfin_kodi/monitor.py @@ -117,11 +117,11 @@ def onNotification(self, sender, method, data): LOG.exception(error) server = Jellyfin() - server = server.get_client() + server_client = server.get_client() if method == 'Play': - items = server.jellyfin.get_items(data['ItemIds']) + items = server_client.jellyfin.get_items(data['ItemIds']) PlaylistWorker(data.get('ServerId'), items, data['PlayCommand'] == 'PlayNow', data.get('StartPositionTicks', 0), data.get('AudioStreamIndex'), @@ -130,7 +130,7 @@ def onNotification(self, sender, method, data): # TODO no clue if this is called by anything elif method == 'PlayPlaylist': - server.jellyfin.post_session(server.config.data['app.session'], "Playing", { + server_client.jellyfin.post_session(server_client.config.data['app.session'], "Playing", { 'PlayCommand': "PlayNow", 'ItemIds': data['Id'], 'StartPositionTicks': 0 @@ -149,14 +149,14 @@ def onNotification(self, sender, method, data): self.server_instance(data['ServerId']) elif method == 'AddUser': - server.jellyfin.session_add_user(server.config.data['app.session'], data['Id'], data['Add']) - self.additional_users(server) + server_client.jellyfin.session_add_user(server_client.config.data['app.session'], data['Id'], data['Add']) + self.additional_users(server_client) elif method == 'Player.OnPlay': - on_play(data, server) + on_play(data, server_client) elif method == 'VideoLibrary.OnUpdate': - on_update(data, server) + on_update(data, server_client) def server_instance(self, server_id=None): diff --git a/jellyfin_kodi/objects/kodi/kodi.py b/jellyfin_kodi/objects/kodi/kodi.py index 1782425a7..39eeaeae2 100644 --- a/jellyfin_kodi/objects/kodi/kodi.py +++ b/jellyfin_kodi/objects/kodi/kodi.py @@ -1,6 +1,9 @@ # -*- coding: utf-8 -*- from __future__ import division, absolute_import, print_function, unicode_literals +from abc import ABCMeta +from sqlite3 import Cursor + ################################################################################################## from ...helper import values, LazyLogger @@ -15,7 +18,8 @@ ################################################################################################## -class Kodi(object): +class Kodi(metaclass=ABCMeta): + cursor: Cursor def __init__(self): self.artwork = artwork.Artwork(self.cursor) diff --git a/jellyfin_kodi/objects/music.py b/jellyfin_kodi/objects/music.py index 1ecf68779..d458465d4 100644 --- a/jellyfin_kodi/objects/music.py +++ b/jellyfin_kodi/objects/music.py @@ -4,6 +4,7 @@ ################################################################################################## import datetime +from typing import List from ..database import jellyfin_db, queries as QUEM from ..helper import api, stop, validate, jellyfin_item, values, Local, LazyLogger @@ -551,7 +552,7 @@ def get_child(self, item_id, e_item): ''' Get all child elements from tv show jellyfin id. ''' obj = {'Id': item_id} - child = [] + child: List[str] = [] try: obj['KodiId'] = e_item[0] diff --git a/jellyfin_kodi/objects/musicvideos.py b/jellyfin_kodi/objects/musicvideos.py index 93cc7b607..74532f983 100644 --- a/jellyfin_kodi/objects/musicvideos.py +++ b/jellyfin_kodi/objects/musicvideos.py @@ -5,6 +5,7 @@ import datetime import re +from typing import List from six.moves.urllib.parse import urlencode from kodi_six.utils import py2_encode @@ -122,7 +123,7 @@ def musicvideo(self, item, e_item): if search: obj['Index'] = search.group() - tags = [] + tags: List[str] = [] tags.extend(obj['Tags'] or []) tags.append(obj['LibraryName']) diff --git a/jellyfin_kodi/objects/tvshows.py b/jellyfin_kodi/objects/tvshows.py index 3b169e1b0..d9c3f054d 100644 --- a/jellyfin_kodi/objects/tvshows.py +++ b/jellyfin_kodi/objects/tvshows.py @@ -5,6 +5,7 @@ import sqlite3 from ntpath import dirname +from typing import List from six.moves.urllib.parse import urlencode from kodi_six.utils import py2_encode @@ -101,7 +102,7 @@ def tvshow(self, item, e_item): if obj['Premiere']: obj['Premiere'] = str(Local(obj['Premiere'])).split('.')[0].replace('T', " ") - tags = [] + tags: List[str] = [] tags.extend(obj['Tags'] or []) tags.append(obj['LibraryName']) @@ -623,7 +624,7 @@ def get_child(self, item_id, e_item): ''' Get all child elements from tv show jellyfin id. ''' obj = {'Id': item_id} - child = [] + child: List[str] = [] try: obj['KodiId'] = e_item[0] diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 000000000..526cc57f5 --- /dev/null +++ b/mypy.ini @@ -0,0 +1,4 @@ +[mypy] +check_untyped_defs = True +warn_unused_configs = True +files = .