mirror of
https://github.com/jellyfin/jellyfin-kodi.git
synced 2024-12-25 10:16:11 +00:00
Merge pull request #366 from oddstr13/pr-json-filter-1
Filter keys containing None values from dictionaries returned from the server
This commit is contained in:
commit
4e2c8a0af3
20 changed files with 214 additions and 26 deletions
|
@ -1,9 +1,11 @@
|
||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
import yaml
|
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
|
||||||
def indent(elem, level=0):
|
def indent(elem, level=0):
|
||||||
'''
|
'''
|
||||||
Nicely formats output xml with newlines and spaces
|
Nicely formats output xml with newlines and spaces
|
||||||
|
@ -23,6 +25,7 @@ def indent(elem, level=0):
|
||||||
if level and (not elem.tail or not elem.tail.strip()):
|
if level and (not elem.tail or not elem.tail.strip()):
|
||||||
elem.tail = i
|
elem.tail = i
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
py_version = sys.argv[1]
|
py_version = sys.argv[1]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
|
|
66
.gitignore
vendored
66
.gitignore
vendored
|
@ -1,12 +1,72 @@
|
||||||
*.pyo
|
# Byte-compiled / optimized / DLL files
|
||||||
__pycache__/
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
pip-wheel-metadata/
|
||||||
|
share/python-wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
MANIFEST
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.nox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
*.py,cover
|
||||||
|
.hypothesis/
|
||||||
|
.pytest_cache/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
__local__/
|
__local__/
|
||||||
machine_guid
|
machine_guid
|
||||||
/resources/media/Thumbs.db
|
Thumbs.db
|
||||||
|
|
||||||
.idea/
|
.idea/
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.vscode/
|
.vscode/
|
||||||
pyinstrument/
|
pyinstrument/
|
||||||
pyinstrument_cext.so
|
|
||||||
|
# Now managed by templates
|
||||||
addon.xml
|
addon.xml
|
||||||
|
|
||||||
|
*.log
|
||||||
|
|
|
@ -17,8 +17,8 @@ sys.path.insert(0, __base__)
|
||||||
|
|
||||||
#################################################################################################
|
#################################################################################################
|
||||||
|
|
||||||
from entrypoint import Context # noqa: F402
|
from entrypoint import Context # noqa: E402
|
||||||
from helper import LazyLogger # noqa: F402
|
from helper import LazyLogger # noqa: E402
|
||||||
|
|
||||||
#################################################################################################
|
#################################################################################################
|
||||||
|
|
||||||
|
|
|
@ -17,8 +17,8 @@ sys.path.insert(0, __base__)
|
||||||
|
|
||||||
#################################################################################################
|
#################################################################################################
|
||||||
|
|
||||||
from entrypoint import Context # noqa: F402
|
from entrypoint import Context # noqa: E402
|
||||||
from helper import LazyLogger # noqa: F402
|
from helper import LazyLogger # noqa: E402
|
||||||
|
|
||||||
#################################################################################################
|
#################################################################################################
|
||||||
|
|
||||||
|
|
|
@ -17,8 +17,8 @@ sys.path.insert(0, __base__)
|
||||||
|
|
||||||
#################################################################################################
|
#################################################################################################
|
||||||
|
|
||||||
from entrypoint import Events # noqa: F402
|
from entrypoint import Events # noqa: E402
|
||||||
from helper import LazyLogger # noqa: F402
|
from helper import LazyLogger # noqa: E402
|
||||||
|
|
||||||
#################################################################################################
|
#################################################################################################
|
||||||
|
|
||||||
|
|
|
@ -224,6 +224,7 @@ def get_library_items(library_id, item_type):
|
||||||
|
|
||||||
return _get(url, params)
|
return _get(url, params)
|
||||||
|
|
||||||
|
|
||||||
def get_albums_by_artist(artist_id, basic=False):
|
def get_albums_by_artist(artist_id, basic=False):
|
||||||
|
|
||||||
params = {
|
params = {
|
||||||
|
|
|
@ -435,13 +435,13 @@ class FullSync(object):
|
||||||
obj.artist(artist)
|
obj.artist(artist)
|
||||||
|
|
||||||
# Get all albums for each artist
|
# Get all albums for each artist
|
||||||
artist_albums = [ album for album in albums if artist_name in album.get('Artists') ]
|
artist_albums = [album for album in albums if artist_name in album.get('Artists')]
|
||||||
for album in artist_albums:
|
for album in artist_albums:
|
||||||
# Add album to database
|
# Add album to database
|
||||||
obj.album(album)
|
obj.album(album)
|
||||||
album_id = album.get('Id')
|
album_id = album.get('Id')
|
||||||
# Get all songs in each album
|
# Get all songs in each album
|
||||||
album_songs = [ song for song in songs if album_id == song.get('AlbumId') ]
|
album_songs = [song for song in songs if album_id == song.get('AlbumId')]
|
||||||
for song in album_songs:
|
for song in album_songs:
|
||||||
dialog.update(percent,
|
dialog.update(percent,
|
||||||
message="%s/%s/%s" % (artist_name, album['Name'][:7], song['Name'][:7]))
|
message="%s/%s/%s" % (artist_name, album['Name'][:7], song['Name'][:7]))
|
||||||
|
|
|
@ -435,7 +435,7 @@ class API(object):
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
return response.json()
|
return response.json()
|
||||||
else:
|
else:
|
||||||
return { 'Status_Code': response.status_code }
|
return {'Status_Code': response.status_code}
|
||||||
|
|
||||||
def get_public_info(self, server_address):
|
def get_public_info(self, server_address):
|
||||||
response = self.send_request(server_address, "system/info/public")
|
response = self.send_request(server_address, "system/info/public")
|
||||||
|
|
|
@ -11,6 +11,7 @@ from six import string_types, ensure_str
|
||||||
from helper.utils import JsonDebugPrinter
|
from helper.utils import JsonDebugPrinter
|
||||||
from helper import LazyLogger
|
from helper import LazyLogger
|
||||||
from helper.exceptions import HTTPException
|
from helper.exceptions import HTTPException
|
||||||
|
from jellyfin.utils import clean_none_dict_values
|
||||||
|
|
||||||
#################################################################################################
|
#################################################################################################
|
||||||
|
|
||||||
|
@ -56,7 +57,7 @@ class HTTP(object):
|
||||||
else:
|
else:
|
||||||
LOG.debug("Server address not set")
|
LOG.debug("Server address not set")
|
||||||
|
|
||||||
if '{UserId}'in string:
|
if '{UserId}' in string:
|
||||||
if self.config.data.get('auth.user_id', None):
|
if self.config.data.get('auth.user_id', None):
|
||||||
string = string.replace("{UserId}", self.config.data['auth.user_id'])
|
string = string.replace("{UserId}", self.config.data['auth.user_id'])
|
||||||
else:
|
else:
|
||||||
|
@ -161,7 +162,7 @@ class HTTP(object):
|
||||||
LOG.debug("---<[ http ][%s ms]", elapsed)
|
LOG.debug("---<[ http ][%s ms]", elapsed)
|
||||||
LOG.debug(JsonDebugPrinter(response))
|
LOG.debug(JsonDebugPrinter(response))
|
||||||
|
|
||||||
return response
|
return clean_none_dict_values(response)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
43
jellyfin_kodi/jellyfin/utils.py
Normal file
43
jellyfin_kodi/jellyfin/utils.py
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
from six import string_types
|
||||||
|
from six.moves import collections_abc
|
||||||
|
|
||||||
|
|
||||||
|
def clean_none_dict_values(obj):
|
||||||
|
"""
|
||||||
|
Recursively remove keys with a value of None
|
||||||
|
"""
|
||||||
|
if not isinstance(obj, collections_abc.Iterable) or isinstance(obj, string_types):
|
||||||
|
return obj
|
||||||
|
|
||||||
|
queue = [obj]
|
||||||
|
|
||||||
|
while queue:
|
||||||
|
item = queue.pop()
|
||||||
|
|
||||||
|
if isinstance(item, collections_abc.Mapping):
|
||||||
|
mutable = isinstance(item, collections_abc.MutableMapping)
|
||||||
|
remove = []
|
||||||
|
|
||||||
|
for key, value in item.items():
|
||||||
|
if value is None and mutable:
|
||||||
|
remove.append(key)
|
||||||
|
|
||||||
|
elif isinstance(value, string_types):
|
||||||
|
continue
|
||||||
|
|
||||||
|
elif isinstance(value, collections_abc.Iterable):
|
||||||
|
queue.append(value)
|
||||||
|
|
||||||
|
if mutable:
|
||||||
|
# Remove keys with None value
|
||||||
|
for key in remove:
|
||||||
|
item.pop(key)
|
||||||
|
|
||||||
|
elif isinstance(item, collections_abc.Iterable):
|
||||||
|
for value in item:
|
||||||
|
if value is None or isinstance(value, string_types):
|
||||||
|
continue
|
||||||
|
elif isinstance(value, collections_abc.Iterable):
|
||||||
|
queue.append(value)
|
||||||
|
|
||||||
|
return obj
|
|
@ -376,7 +376,7 @@ class Library(threading.Thread):
|
||||||
include = []
|
include = []
|
||||||
filters = ["tvshows", "boxsets", "musicvideos", "music", "movies"]
|
filters = ["tvshows", "boxsets", "musicvideos", "music", "movies"]
|
||||||
sync = get_sync()
|
sync = get_sync()
|
||||||
whitelist = [ x.replace('Mixed:', "") for x in sync['Whitelist'] ]
|
whitelist = [x.replace('Mixed:', "") for x in sync['Whitelist']]
|
||||||
LOG.info("--[ retrieve changes ] %s", last_sync)
|
LOG.info("--[ retrieve changes ] %s", last_sync)
|
||||||
|
|
||||||
# Get the item type of each synced library and build list of types to request
|
# Get the item type of each synced library and build list of types to request
|
||||||
|
@ -395,7 +395,7 @@ class Library(threading.Thread):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Get list of updates from server for synced library types and populate work queues
|
# Get list of updates from server for synced library types and populate work queues
|
||||||
result = self.server.jellyfin.get_sync_queue(last_sync, ",".join([ x for x in query_filter ]))
|
result = self.server.jellyfin.get_sync_queue(last_sync, ",".join([x for x in query_filter]))
|
||||||
|
|
||||||
if result is None:
|
if result is None:
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -191,13 +191,13 @@ class Movies(KodiDb):
|
||||||
if validate_dvd_dir(obj['Path'] + obj['Filename']):
|
if validate_dvd_dir(obj['Path'] + obj['Filename']):
|
||||||
obj['Path'] = obj['Path'] + obj['Filename'] + '/VIDEO_TS/'
|
obj['Path'] = obj['Path'] + obj['Filename'] + '/VIDEO_TS/'
|
||||||
obj['Filename'] = 'VIDEO_TS.IFO'
|
obj['Filename'] = 'VIDEO_TS.IFO'
|
||||||
LOG.debug("DVD directry %s",obj['Path'])
|
LOG.debug("DVD directry %s", obj['Path'])
|
||||||
|
|
||||||
'''check bluray directries and point it to ./BDMV/index.bdmv'''
|
'''check bluray directries and point it to ./BDMV/index.bdmv'''
|
||||||
if validate_bluray_dir(obj['Path'] + obj['Filename']):
|
if validate_bluray_dir(obj['Path'] + obj['Filename']):
|
||||||
obj['Path'] = obj['Path'] + obj['Filename'] + '/BDMV/'
|
obj['Path'] = obj['Path'] + obj['Filename'] + '/BDMV/'
|
||||||
obj['Filename'] = 'index.bdmv'
|
obj['Filename'] = 'index.bdmv'
|
||||||
LOG.debug("Bluray directry %s",obj['Path'])
|
LOG.debug("Bluray directry %s", obj['Path'])
|
||||||
|
|
||||||
else:
|
else:
|
||||||
obj['Path'] = "plugin://plugin.video.jellyfin/%s/" % obj['LibraryId']
|
obj['Path'] = "plugin://plugin.video.jellyfin/%s/" % obj['LibraryId']
|
||||||
|
|
|
@ -160,9 +160,9 @@ class Player(xbmc.Player):
|
||||||
|
|
||||||
def set_audio_subs(self, audio=None, subtitle=None):
|
def set_audio_subs(self, audio=None, subtitle=None):
|
||||||
if audio:
|
if audio:
|
||||||
audio=int(audio)
|
audio = int(audio)
|
||||||
if subtitle:
|
if subtitle:
|
||||||
subtitle=int(subtitle)
|
subtitle = int(subtitle)
|
||||||
|
|
||||||
''' Only for after playback started
|
''' Only for after playback started
|
||||||
'''
|
'''
|
||||||
|
|
13
requirements-dev.txt
Normal file
13
requirements-dev.txt
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
setuptools >= 44.1.1 # Old setuptools causes script.module.addon.signals to fail installing
|
||||||
|
six >= 1.13
|
||||||
|
python-dateutil >= 2.8.1
|
||||||
|
requests >= 2.22
|
||||||
|
futures >= 2.2; python_version < '3.0'
|
||||||
|
git+https://github.com/oddstr13/Kodistubs@python3 # Kodistubs >= 18
|
||||||
|
git+https://github.com/romanvm/kodi.six
|
||||||
|
git+https://github.com/ruuk/script.module.addon.signals
|
||||||
|
|
||||||
|
pytest >= 4.6.11
|
||||||
|
coverage >= 5.2
|
||||||
|
flake8 >= 3.8
|
||||||
|
flake8-import-order >= 0.18
|
|
@ -18,9 +18,9 @@ sys.path.insert(0, __base__)
|
||||||
|
|
||||||
#################################################################################################
|
#################################################################################################
|
||||||
|
|
||||||
from entrypoint import Service # noqa: F402
|
from entrypoint import Service # noqa: E402
|
||||||
from helper.utils import settings # noqa: F402
|
from helper.utils import settings # noqa: E402
|
||||||
from helper import LazyLogger # noqa: F402
|
from helper import LazyLogger # noqa: E402
|
||||||
|
|
||||||
#################################################################################################
|
#################################################################################################
|
||||||
|
|
||||||
|
|
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
51
tests/test_clean_none_dict_values.py
Normal file
51
tests/test_clean_none_dict_values.py
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
sys.path.insert(0, 'jellyfin_kodi')
|
||||||
|
|
||||||
|
from jellyfin.utils import clean_none_dict_values # noqa: E402
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("obj,expected", [
|
||||||
|
(None, None),
|
||||||
|
([None, 1, 2, 3, None, 4], [None, 1, 2, 3, None, 4]),
|
||||||
|
({'foo': None, 'bar': 123}, {'bar': 123}),
|
||||||
|
({
|
||||||
|
'dict': {
|
||||||
|
'empty': None,
|
||||||
|
'string': "Hello, Woorld!",
|
||||||
|
},
|
||||||
|
'number': 123,
|
||||||
|
'list': [
|
||||||
|
None,
|
||||||
|
123,
|
||||||
|
"foo",
|
||||||
|
{
|
||||||
|
'empty': None,
|
||||||
|
'number': 123,
|
||||||
|
'string': "foo",
|
||||||
|
'list': [],
|
||||||
|
'dict': {},
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}, {
|
||||||
|
'dict': {
|
||||||
|
'string': "Hello, Woorld!",
|
||||||
|
},
|
||||||
|
'number': 123,
|
||||||
|
'list': [
|
||||||
|
None,
|
||||||
|
123,
|
||||||
|
"foo",
|
||||||
|
{
|
||||||
|
'number': 123,
|
||||||
|
'string': "foo",
|
||||||
|
'list': [],
|
||||||
|
'dict': {},
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
def test_clean_none_dict_values(obj, expected):
|
||||||
|
assert clean_none_dict_values(obj) == expected
|
16
tox.ini
16
tox.ini
|
@ -6,3 +6,19 @@ extend-ignore =
|
||||||
I202
|
I202
|
||||||
per-file-ignores =
|
per-file-ignores =
|
||||||
*/__init__.py: F401
|
*/__init__.py: F401
|
||||||
|
|
||||||
|
[pytest]
|
||||||
|
minversion = 4.6
|
||||||
|
testpaths =
|
||||||
|
tests
|
||||||
|
|
||||||
|
[coverage:run]
|
||||||
|
source =
|
||||||
|
jellyfin_kodi
|
||||||
|
context.py
|
||||||
|
context_play.py
|
||||||
|
default.py
|
||||||
|
service.py
|
||||||
|
.config/generate_xml.py
|
||||||
|
omit = tests/*
|
||||||
|
command_line = -m pytest
|
||||||
|
|
Loading…
Reference in a new issue