From b74706d6501e3bbffbb82acb476832719d7992f7 Mon Sep 17 00:00:00 2001
From: romanvm
Date: Wed, 13 Dec 2023 21:26:34 +0000
Subject: [PATCH] [metadata.tvmaze] 1.3.5
---
metadata.tvmaze/addon.xml | 5 +-
metadata.tvmaze/libs/actions.py | 42 ++++---
metadata.tvmaze/libs/cache_service.py | 17 +--
metadata.tvmaze/libs/data_service.py | 12 +-
metadata.tvmaze/libs/exception_logger.py | 137 +++++++++++++++--------
metadata.tvmaze/libs/imdb_rating.py | 6 +-
metadata.tvmaze/libs/tvmaze_api.py | 20 ++--
metadata.tvmaze/libs/utils.py | 53 ++++++---
metadata.tvmaze/main.py | 7 +-
9 files changed, 189 insertions(+), 110 deletions(-)
diff --git a/metadata.tvmaze/addon.xml b/metadata.tvmaze/addon.xml
index 656d5bd2d..267bf84ff 100644
--- a/metadata.tvmaze/addon.xml
+++ b/metadata.tvmaze/addon.xml
@@ -1,7 +1,7 @@
@@ -20,8 +20,7 @@ We provide an API that can be used by anyone or service like Kodi to retrieve TV
https://www.tvmaze.com
- 1.3.4:
-- Internal changes.
+ 1.3.4: Fix incompatibility with filename tags in Kodi "Omega".
true
diff --git a/metadata.tvmaze/libs/actions.py b/metadata.tvmaze/libs/actions.py
index 2f642e087..14dce455e 100644
--- a/metadata.tvmaze/libs/actions.py
+++ b/metadata.tvmaze/libs/actions.py
@@ -16,6 +16,7 @@
"""Plugin route actions"""
import json
+import logging
import sys
from typing import Optional
from urllib import parse as urllib_parse
@@ -24,7 +25,7 @@
import xbmcplugin
from . import tvmaze_api, data_service
-from .utils import logger, get_episode_order, ADDON
+from .utils import get_episode_order, ADDON
HANDLE = int(sys.argv[1])
@@ -60,7 +61,7 @@ def parse_nfo_file(nfo: str, full_nfo: bool):
:param full_nfo: use the info from an NFO and not to try to get the info by the scraper
"""
is_tvshow_nfo = True
- logger.debug(f'Trying to parse NFO file:\n{nfo}')
+ logging.debug('Trying to parse NFO file:\n%s', nfo)
info = None
if '' in nfo:
if full_nfo:
@@ -94,33 +95,40 @@ def parse_nfo_file(nfo: str, full_nfo: bool):
)
-def get_details(show_id: str, default_rating: str) -> None:
+def get_details(show_id: Optional[str], default_rating: str, unique_ids: Optional[str] = None) -> None:
"""Get details about a specific show"""
- logger.debug(f'Getting details for show id {show_id}')
+ logging.debug('Getting details for show id %s', show_id)
+ if not show_id and unique_ids is not None:
+ show_id = data_service.parse_json_episogeguide(unique_ids)
+ if not show_id:
+ xbmcplugin.setResolvedUrl(HANDLE, False, xbmcgui.ListItem(offscreen=True))
+ return
show_info = tvmaze_api.load_show_info(show_id)
if show_info is not None:
list_item = xbmcgui.ListItem(show_info['name'], offscreen=True)
list_item = data_service.add_main_show_info(list_item, show_info,
default_rating=default_rating)
xbmcplugin.setResolvedUrl(HANDLE, True, list_item)
- else:
- xbmcplugin.setResolvedUrl(HANDLE, False, xbmcgui.ListItem(offscreen=True))
+ return
+ xbmcplugin.setResolvedUrl(HANDLE, False, xbmcgui.ListItem(offscreen=True))
def get_episode_list(episodeguide: str, episode_order: str) -> None: # pylint: disable=missing-docstring
- logger.debug(f'Getting episode list for episodeguide {episodeguide}, order: {episode_order}')
+ logging.debug('Getting episode list for episodeguide %s, order: %s',
+ episodeguide, episode_order)
show_id = None
if episodeguide.startswith('{'):
show_id = data_service.parse_json_episogeguide(episodeguide)
if show_id is None:
- logger.error(f'Unable to determine TVmaze show ID from episodeguide: {episodeguide}')
+ logging.error(f'Unable to determine TVmaze show ID from episodeguide: %s', episodeguide)
return
if show_id is None and not episodeguide.isdigit():
- logger.warning(f'Invalid episodeguide format: {episodeguide} (probably URL).')
+ logging.warning('Invalid episodeguide format: %s (probably URL).', episodeguide)
show_id = data_service.parse_url_episodeguide(episodeguide)
if show_id is None and episodeguide.isdigit():
- logger.warning(f'Invalid episodeguide format: {episodeguide} (a numeric string). '
- f'Please consider re-scanning the show to update episodeguide record.')
+ logging.warning('Invalid episodeguide format: %s (a numeric string). '
+ 'Please consider re-scanning the show to update episodeguide record.',
+ episodeguide)
show_id = episodeguide
if show_id is not None:
episodes_map = data_service.get_episodes_map(show_id, episode_order)
@@ -147,7 +155,7 @@ def get_episode_list(episodeguide: str, episode_order: str) -> None: # pylint:
def get_episode_details(encoded_ids: str, episode_order: str) -> None: # pylint: disable=missing-docstring
encoded_ids = urllib_parse.unquote(encoded_ids)
decoded_ids = dict(urllib_parse.parse_qsl(encoded_ids))
- logger.debug(f'Getting episode details for {decoded_ids}')
+ logging.debug('Getting episode details for %s', decoded_ids)
episode_info = data_service.get_episode_info(decoded_ids['show_id'],
decoded_ids['episode_id'],
decoded_ids['season'],
@@ -167,7 +175,7 @@ def get_artwork(show_id: str) -> None:
:param show_id: default unique ID set by setUniqueIDs() method
"""
- logger.debug(f'Getting artwork for show ID {show_id}')
+ logging.debug('Getting artwork for show ID %s', show_id)
if show_id:
show_info = tvmaze_api.load_show_info(show_id)
if show_info is not None:
@@ -186,9 +194,9 @@ def router(paramstring: str) -> None:
:raises RuntimeError: on unknown call action
"""
params = dict(urllib_parse.parse_qsl(paramstring))
- logger.debug(f'Called addon with params: {sys.argv}')
+ logging.debug('Called addon with params: %s', str(sys.argv))
path_settings = json.loads(params.get('pathSettings') or '{}')
- logger.debug(f'Path settings: {path_settings}')
+ logging.debug('Path settings: %s', path_settings)
episode_order = get_episode_order(path_settings)
default_rating = path_settings.get('default_rating')
if default_rating is None:
@@ -201,7 +209,9 @@ def router(paramstring: str) -> None:
elif params['action'].lower() == 'nfourl':
parse_nfo_file(params['nfo'], full_nfo)
elif params['action'] == 'getdetails':
- get_details(params['url'], default_rating)
+ url = params.get('url')
+ unique_ids = params.get('uniqueIDs')
+ get_details(url, default_rating, unique_ids)
elif params['action'] == 'getepisodelist':
get_episode_list(params['url'], episode_order)
elif params['action'] == 'getepisodedetails':
diff --git a/metadata.tvmaze/libs/cache_service.py b/metadata.tvmaze/libs/cache_service.py
index 4cb5b4a00..6cd5212d0 100644
--- a/metadata.tvmaze/libs/cache_service.py
+++ b/metadata.tvmaze/libs/cache_service.py
@@ -16,6 +16,7 @@
"""Cache-related functionality"""
import json
+import logging
import os
import time
from typing import Optional, Text, Dict, Any, Union
@@ -23,14 +24,14 @@
import xbmcgui
import xbmcvfs
-from .utils import ADDON_ID, logger
+from .utils import ADDON_ID
EPISODES_CACHE_TTL = 60 * 10 # 10 minutes
class MemoryCache:
_instance = None
- CACHE_KEY = '__tvmaze_scraper__'
+ CACHE_KEY = f'__{ADDON_ID}_cache__'
def __new__(cls):
if cls._instance is None:
@@ -52,17 +53,17 @@ def set(self, obj_id: Union[int, str], obj: Any) -> None:
def get(self, obj_id: Union[int, str]) -> Optional[Any]:
cache_json = self._window.getProperty(self.CACHE_KEY)
if not cache_json:
- logger.debug('Memory cache empty')
+ logging.debug('Memory cache empty')
return None
try:
cache = json.loads(cache_json)
except ValueError as exc:
- logger.debug(f'Memory cache error: {exc}')
+ logging.debug(f'Memory cache error: {exc}')
return None
if cache['id'] != obj_id or time.time() - cache['timestamp'] > EPISODES_CACHE_TTL:
- logger.debug('Memory cache miss')
+ logging.debug('Memory cache miss')
return None
- logger.debug('Memory cache hit')
+ logging.debug('Memory cache hit')
return cache['object']
@@ -110,8 +111,8 @@ def load_show_info_from_cache(show_id: Union[int, str]) -> Optional[Dict[str, An
with open(os.path.join(CACHE_DIR, file_name), 'r', encoding='utf-8') as fo:
cache_json = fo.read()
show_info = json.loads(cache_json)
- logger.debug('Show info cache hit')
+ logging.debug('Show info cache hit')
return show_info
except (IOError, EOFError, ValueError) as exc:
- logger.debug(f'Cache error: {type(exc)} {exc}')
+ logging.debug('Cache error: %s %s', type(exc), exc)
return None
diff --git a/metadata.tvmaze/libs/data_service.py b/metadata.tvmaze/libs/data_service.py
index 97deb2df7..3c1cb9bea 100644
--- a/metadata.tvmaze/libs/data_service.py
+++ b/metadata.tvmaze/libs/data_service.py
@@ -15,6 +15,7 @@
"""Functions to process data"""
import json
+import logging
import re
from collections import defaultdict
from typing import Optional, Dict, List, Any, Sequence, NamedTuple
@@ -26,7 +27,6 @@
from xbmcgui import ListItem
from . import tvmaze_api, cache_service as cache
-from .utils import logger
InfoType = Dict[str, Any] # pylint: disable=invalid-name
@@ -47,7 +47,7 @@
('', '[/I]'),
('
', '[CR]'),
)
-SUPPORTED_EXTERNAL_IDS = ('tvdb', 'imdb')
+SUPPORTED_EXTERNAL_IDS = ('tvdb', 'thetvdb', 'imdb')
class UrlParseResult(NamedTuple):
@@ -116,7 +116,7 @@ def get_episode_info(show_id: str,
key = f'{episode_id}_{season}_{episode}'
episode_info = episodes_map[key]
except KeyError as exc:
- logger.error(f'Unable to retrieve episode info: {exc}')
+ logging.error('Unable to retrieve episode info: %s', exc)
if episode_info is None:
episode_info = tvmaze_api.load_episode_info(episode_id)
return episode_info
@@ -313,9 +313,9 @@ def parse_url_nfo_contents(nfo: str) -> Optional[UrlParseResult]:
if show_id_match is not None:
provider = show_id_match.group(1)
show_id = show_id_match.group(2)
- logger.debug(f'Matched show ID {show_id} by regexp "{regexp}"')
+ logging.debug('Matched show ID %s by regexp "%s"', show_id, regexp)
return UrlParseResult(provider, show_id)
- logger.debug('Unable to find show ID in an NFO file')
+ logging.debug('Unable to find show ID in an NFO file')
return None
@@ -403,7 +403,7 @@ def _filter_by_year(shows: List[InfoType], year: str) -> Optional[InfoType]:
def search_show(title: str, year: str) -> Sequence[InfoType]:
- logger.debug(f'Searching for TV show {title} ({year})')
+ logging.debug(f'Searching for TV show %s (%s)', title, year)
raw_search_results = tvmaze_api.search_show(title)
search_results = [res['show'] for res in raw_search_results]
if len(search_results) > 1 and year:
diff --git a/metadata.tvmaze/libs/exception_logger.py b/metadata.tvmaze/libs/exception_logger.py
index 40d92e850..928dd8086 100644
--- a/metadata.tvmaze/libs/exception_logger.py
+++ b/metadata.tvmaze/libs/exception_logger.py
@@ -1,4 +1,4 @@
-# (c) Roman Miroshnychenko 2020
+# (c) Roman Miroshnychenko 2023
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -13,16 +13,19 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
"""Exception logger with extended diagnostic info"""
+
import inspect
import sys
from contextlib import contextmanager
from platform import uname
from pprint import pformat
-from typing import List, Dict, Callable, Generator, Any
+from typing import Any, Dict, Callable, Generator, Iterable, Optional
import xbmc
-from .utils import logger
+
+def _log_error(message: str) -> None:
+ xbmc.log(message, level=xbmc.LOGERROR)
def _format_vars(variables: Dict[str, Any]) -> str:
@@ -37,15 +40,15 @@ def _format_vars(variables: Dict[str, Any]) -> str:
var_list.sort(key=lambda i: i[0])
lines = []
for var, val in var_list:
- lines.append(f'{var} = {pformat(val, indent=4)}')
+ lines.append(f'{var} = {pformat(val)}')
return '\n'.join(lines)
-def _format_code_context(code_context: List[str], lineno: int, index: int) -> str:
+def _format_code_context(frame_info: inspect.FrameInfo) -> str:
context = ''
- if code_context is not None:
- for i, line in enumerate(code_context, lineno - index):
- if i == lineno:
+ if frame_info.code_context is not None:
+ for i, line in enumerate(frame_info.code_context, frame_info.lineno - frame_info.index):
+ if i == frame_info.lineno:
context += f'{str(i).rjust(5)}:>{line}'
else:
context += f'{str(i).rjust(5)}: {line}'
@@ -64,40 +67,96 @@ def _format_code_context(code_context: List[str], lineno: int, index: int) -> st
"""
-def _format_frame_info(frame_info: tuple) -> str:
+def _format_frame_info(frame_info: inspect.FrameInfo) -> str:
return FRAME_INFO_TEMPLATE.format(
- file_path=frame_info[1],
- lineno=frame_info[2],
- code_context=_format_code_context(frame_info[4], frame_info[2],
- frame_info[5]),
- local_vars=_format_vars(frame_info[0].f_locals)
+ file_path=frame_info.filename,
+ lineno=frame_info.lineno,
+ code_context=_format_code_context(frame_info),
+ local_vars=_format_vars(frame_info.frame.f_locals)
)
+STACK_TRACE_TEMPLATE = """
+####################################################################################################
+ Stack Trace
+====================================================================================================
+{stack_trace}
+************************************* End of diagnostic info ***************************************
+"""
+
+
+def _format_stack_trace(frames: Iterable[inspect.FrameInfo]) -> str:
+ stack_trace = ''
+ for frame_info in frames:
+ stack_trace += _format_frame_info(frame_info)
+ return STACK_TRACE_TEMPLATE.format(stack_trace=stack_trace)
+
+
EXCEPTION_TEMPLATE = """
-*********************************** Unhandled exception detected ***********************************
####################################################################################################
- Diagnostic info
+ Exception Diagnostic Info
----------------------------------------------------------------------------------------------------
-Exception type : {exc_type}
-Exception value : {exc}
-System info : {system_info}
-Python version : {python_version}
-Kodi version : {kodi_version}
-sys.argv : {sys_argv}
+Exception type : {exc_type}
+Exception message : {exc}
+System info : {system_info}
+Python version : {python_version}
+Kodi version : {kodi_version}
+sys.argv : {sys_argv}
----------------------------------------------------------------------------------------------------
sys.path:
{sys_path}
-####################################################################################################
- Stack Trace
-====================================================================================================
-{stack_trace}
-************************************* End of diagnostic info ***************************************
+{stack_trace_info}
"""
+def format_trace(frames_to_exclude: int = 1) -> str:
+ """
+ Returns a pretty stack trace with code context and local variables
+
+ Stack trace info includes the following:
+
+ * File path and line number
+ * Code fragment
+ * Local variables
+
+ It allows to inspect execution state at the point of this function call
+
+ :param frames_to_exclude: How many top frames are excluded from the trace
+ to skip unnecessary info. Since each function call creates a stack frame
+ you need to exclude at least this function frame.
+ """
+ frames = inspect.stack(5)[frames_to_exclude:]
+ return _format_stack_trace(reversed(frames))
+
+
+def format_exception(exc_obj: Optional[Exception] = None) -> str:
+ """
+ Returns a pretty exception stack trace with code context and local variables
+
+ :param exc_obj: exception object (optional)
+ :raises ValueError: if no exception is being handled
+ """
+ if exc_obj is None:
+ _, exc_obj, _ = sys.exc_info()
+ if exc_obj is None:
+ raise ValueError('No exception is currently being handled')
+ stack_trace = inspect.getinnerframes(exc_obj.__traceback__, context=5)
+ stack_trace_info = _format_stack_trace(stack_trace)
+ message = EXCEPTION_TEMPLATE.format(
+ exc_type=exc_obj.__class__.__name__,
+ exc=exc_obj,
+ system_info=uname(),
+ python_version=sys.version.replace('\n', ' '),
+ kodi_version=xbmc.getInfoLabel('System.BuildVersion'),
+ sys_argv=pformat(sys.argv),
+ sys_path=pformat(sys.path),
+ stack_trace_info=stack_trace_info
+ )
+ return message
+
+
@contextmanager
-def log_exception(logger_func: Callable[[str], None] = logger.error) -> Generator[None, None, None]:
+def catch_exception(logger_func: Callable[[str], None] = _log_error) -> Generator[None, None, None]:
"""
Diagnostic helper context manager
@@ -118,7 +177,7 @@ def log_exception(logger_func: Callable[[str], None] = logger.error) -> Generato
Example::
- with debug_exception():
+ with catch_exception():
# Some risky code
raise RuntimeError('Fatal error!')
@@ -128,18 +187,8 @@ def log_exception(logger_func: Callable[[str], None] = logger.error) -> Generato
try:
yield
except Exception as exc:
- stack_trace = ''
- for frame_info in inspect.trace(5):
- stack_trace += _format_frame_info(frame_info)
- message = EXCEPTION_TEMPLATE.format(
- exc_type=exc.__class__.__name__,
- exc=exc,
- system_info=uname(),
- python_version=sys.version.replace('\n', ' '),
- kodi_version=xbmc.getInfoLabel('System.BuildVersion'),
- sys_argv=str(sys.argv),
- sys_path=pformat(sys.path),
- stack_trace=stack_trace
- )
- logger_func(message)
- raise exc
+ message = format_exception(exc)
+ # pylint: disable=line-too-long
+ logger_func('\n*********************************** Unhandled exception detected ***********************************\n'
+ + message)
+ raise
diff --git a/metadata.tvmaze/libs/imdb_rating.py b/metadata.tvmaze/libs/imdb_rating.py
index ac5a9c91d..65d4e4503 100644
--- a/metadata.tvmaze/libs/imdb_rating.py
+++ b/metadata.tvmaze/libs/imdb_rating.py
@@ -14,11 +14,11 @@
# along with this program. If not, see .
import json
+import logging
import re
from typing import Dict, Union, Optional
from . import simple_requests as requests
-from .utils import logger
IMDB_TITLE_URL = 'https://www.imdb.com/title/{}/'
@@ -36,6 +36,6 @@ def get_imdb_rating(imdb_id: str) -> Optional[Dict[str, Union[int, float]]]:
rating = aggregate_rating['ratingValue']
votes = aggregate_rating['ratingCount']
return {'rating': rating, 'votes': votes}
- logger.debug(f'Unable to get IMDB rating for ID {imdb_id}. '
- f'Status: {response.status_code}, response: {response.text}')
+ logging.debug('Unable to get IMDB rating for ID %s. Status: %s, response: %s',
+ imdb_id, response.status_code, response.text)
return None
diff --git a/metadata.tvmaze/libs/tvmaze_api.py b/metadata.tvmaze/libs/tvmaze_api.py
index 5d5f0f3b2..a4b5c0955 100644
--- a/metadata.tvmaze/libs/tvmaze_api.py
+++ b/metadata.tvmaze/libs/tvmaze_api.py
@@ -15,13 +15,13 @@
"""Functions to interact with TVmaze API"""
+import logging
from pprint import pformat
from typing import Text, Optional, Union, List, Dict, Any
from . import cache_service as cache
from . import simple_requests as requests
from .imdb_rating import get_imdb_rating
-from .utils import logger
InfoType = Dict[str, Any] # pylint: disable=invalid-name
@@ -49,12 +49,12 @@ def _load_info(url: str,
:return: API response
:raises requests.exceptions.HTTPError: if any error happens
"""
- logger.debug(f'Calling URL "{url}" with params {params}')
+ logging.debug('Calling URL "%s" with params %s', url, params)
response = requests.get(url, params=params, headers=dict(HEADERS))
if not response.ok:
response.raise_for_status()
json_response = response.json()
- logger.debug(f'TVmaze response:\n{pformat(json_response)}')
+ logging.debug('TVmaze response:\n%s', pformat(json_response))
return json_response
@@ -68,7 +68,7 @@ def search_show(title: str) -> List[InfoType]:
try:
return _load_info(SEARCH_URL, {'q': title})
except requests.HTTPError as exc:
- logger.error(f'TVmaze returned an error: {exc}')
+ logging.error('TVmaze returned an error: %s', exc)
return []
@@ -86,7 +86,7 @@ def load_show_info(show_id: str) -> Optional[InfoType]:
try:
show_info = _load_info(show_info_url, params)
except requests.HTTPError as exc:
- logger.error(f'TVmaze returned an error: {exc}')
+ logging.error('TVmaze returned an error: %s', exc)
return None
if isinstance(show_info['_embedded']['images'], list):
show_info['_embedded']['images'].sort(key=lambda img: img['main'],
@@ -113,7 +113,7 @@ def load_show_info_by_external_id(provider: str, show_id: str) -> Optional[InfoT
try:
return _load_info(SEARCH_BY_EXTERNAL_ID_URL, query)
except requests.HTTPError as exc:
- logger.error(f'TVmaze returned an error: {exc}')
+ logging.error('TVmaze returned an error: %s', exc)
return None
@@ -123,7 +123,7 @@ def _get_alternate_episode_list_id(show_id: str, episode_order: str) -> Optional
try:
alternate_lists = _load_info(url)
except requests.HTTPError as exc:
- logger.error(f'TVmaze returned an error: {exc}')
+ logging.error('TVmaze returned an error: %s', exc)
else:
for episode_list in alternate_lists:
if episode_list.get(episode_order):
@@ -140,7 +140,7 @@ def load_alternate_episode_list(show_id: str, episode_order: str) -> Optional[Li
try:
raw_alternate_episodes = _load_info(url, {'embed': 'episodes'})
except requests.HTTPError as exc:
- logger.error(f'TVmaze returned an error: {exc}')
+ logging.error('TVmaze returned an error: %s', exc)
else:
alternate_episodes = []
for episode in raw_alternate_episodes:
@@ -163,7 +163,7 @@ def load_episode_list(show_id: str, episode_order: str) -> Optional[List[InfoTyp
try:
episode_list = _load_info(episode_list_url, {'specials': '1'})
except requests.HTTPError as exc:
- logger.error(f'TVmaze returned an error: {exc}')
+ logging.error('TVmaze returned an error: %s', exc)
return episode_list
@@ -172,5 +172,5 @@ def load_episode_info(episode_id: Union[str, int]) -> Optional[InfoType]:
try:
return _load_info(url)
except requests.HTTPError as exc:
- logger.error(f'TVmaze returned an error: {exc}')
+ logging.error('TVmaze returned an error: %s', exc)
return None
diff --git a/metadata.tvmaze/libs/utils.py b/metadata.tvmaze/libs/utils.py
index a512fd4b4..34a258a8c 100644
--- a/metadata.tvmaze/libs/utils.py
+++ b/metadata.tvmaze/libs/utils.py
@@ -14,6 +14,7 @@
# along with this program. If not, see .
"""Misc utils"""
+import logging
from typing import Text, Any, Dict
import xbmc
@@ -23,6 +24,8 @@
ADDON_ID = ADDON.getAddonInfo('id')
VERSION = ADDON.getAddonInfo('version')
+LOG_FORMAT = '[{addon_id} v.{addon_version}] {filename}:{lineno} - {message}'
+
EPISODE_ORDER_MAP = {
0: 'default',
1: 'dvd_release',
@@ -34,29 +37,43 @@
}
-class logger:
- log_message_prefix = f'[{ADDON_ID} ({VERSION})]: '
+class KodiLogHandler(logging.Handler):
+ """
+ Logging handler that writes to the Kodi log with correct levels
- @staticmethod
- def log(message: str, level: int = xbmc.LOGDEBUG) -> None:
- message = logger.log_message_prefix + message
- xbmc.log(message, level)
+ It also adds {addon_id} and {addon_version} variables available to log format.
+ """
+ LEVEL_MAP = {
+ logging.NOTSET: xbmc.LOGNONE,
+ logging.DEBUG: xbmc.LOGDEBUG,
+ logging.INFO: xbmc.LOGINFO,
+ logging.WARN: xbmc.LOGWARNING,
+ logging.WARNING: xbmc.LOGWARNING,
+ logging.ERROR: xbmc.LOGERROR,
+ logging.CRITICAL: xbmc.LOGFATAL,
+ }
- @classmethod
- def info(cls, message: str) -> None:
- cls.log(message, xbmc.LOGINFO)
+ def emit(self, record):
+ record.addon_id = ADDON_ID
+ record.addon_version = VERSION
+ message = self.format(record)
+ kodi_log_level = self.LEVEL_MAP.get(record.levelno, xbmc.LOGDEBUG)
+ xbmc.log(message, level=kodi_log_level)
- @classmethod
- def error(cls, message: str) -> None:
- cls.log(message, xbmc.LOGERROR)
- @classmethod
- def debug(cls, message: str) -> None:
- logger.log(message, xbmc.LOGDEBUG)
+def initialize_logging():
+ """
+ Initialize the root logger that writes to the Kodi log
- @classmethod
- def warning(cls, message: str) -> None:
- logger.log(message, xbmc.LOGWARNING)
+ After initialization, you can use Python logging facilities as usual.
+ """
+ logging.basicConfig(
+ format=LOG_FORMAT,
+ style='{',
+ level=logging.DEBUG,
+ handlers=[KodiLogHandler()],
+ force=True
+ )
def get_episode_order(path_settings: Dict[Text, Any]) -> str:
diff --git a/metadata.tvmaze/main.py b/metadata.tvmaze/main.py
index 329a354e0..0ad66536d 100644
--- a/metadata.tvmaze/main.py
+++ b/metadata.tvmaze/main.py
@@ -13,11 +13,14 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
# pylint: disable=missing-docstring
+import logging
import sys
from libs.actions import router
-from libs.exception_logger import log_exception
+from libs.exception_logger import catch_exception
+from libs.utils import initialize_logging
if __name__ == '__main__':
- with log_exception():
+ initialize_logging()
+ with catch_exception(logger_func=logging.error):
router(sys.argv[2][1:])