Skip to content

Commit 0c01791

Browse files
committed
Assorted fixes
This PR includes: - Add docstrings to all methods and functions - Remove disabled import-error and missing-docstring pylint tests - A codecov.yml file for code coverage - Add coverage support to Travis config - Small fixes to setup.py and tox config - Allow PYTHON to be set for Makefile - Unit test fix - Fix SPDX to LGPL-2.1-or-later - Switch to using unicode_literals for Python 2 - Replace RECEIVER global with static variable - Replace getAddonInfo() call with _addon_id() using static variable
1 parent 558f50f commit 0c01791

12 files changed

Lines changed: 104 additions & 57 deletions

File tree

.codecov.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
codecov:
2+
branch: master
3+
coverage:
4+
round: nearest
5+
status:
6+
project:
7+
default:
8+
target: 75%
9+
threshold: 6%
10+
patch: false
11+
comment: false
12+
ignore:
13+
- test/

.gitignore

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
11
# Byte-compiled / optimized / DLL files
22
.coverage
3+
.pytest_cache/
34
.tox/
45
__pycache__/
56
*.py[cod]
67

7-
*.zip
8-
8+
# Packaging
99
build/
1010
dist/
11+
*.zip
12+
13+
# Logs
14+
kodi-addon-checker.log
15+
kodi-addon-checker-report.log
16+
17+
# Temporary
18+
experiment/

.pylintrc

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
[MESSAGES CONTROL]
22
disable=
3-
import-error, # Since xbmc and xbmcaddon are missing
4-
invalid-name, # A lot of names do not follow conventions
3+
invalid-name, # Module name or methods do not follow snake_case convention
54
line-too-long, # We use flake8 for testing line-length
6-
missing-docstring, # No docstrings are used
75
old-style-class,

.travis.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ sudo: false
1111

1212
env:
1313
PYTHONPATH: lib:test
14+
PYTHONIOENCODING: utf-8
1415

1516
install:
1617
- pip install -r requirements.txt
@@ -21,4 +22,7 @@ script:
2122
- pylint lib/AddonSignals.py test/
2223
#- kodi-addon-checker . --branch=krypton
2324
#- kodi-addon-checker . --branch=leia
24-
- python -m unittest discover
25+
- coverage run -m unittest discover
26+
27+
after_success:
28+
- codecov

Makefile

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
export PYTHONPATH := $(CURDIR)/lib:$(CURDIR)/test
2-
addon_xml := addon.xml
2+
PYTHON := python
33

4-
# Collect information to build as sensible package name
5-
name = $(shell xmllint --xpath 'string(/addon/@id)' $(addon_xml))
6-
version = $(shell xmllint --xpath 'string(/addon/@version)' $(addon_xml))
4+
name = $(shell xmllint --xpath 'string(/addon/@id)' addon.xml)
5+
version = $(shell xmllint --xpath 'string(/addon/@version)' addon.xml)
76
git_branch = $(shell git rev-parse --abbrev-ref HEAD)
87
git_hash = $(shell git rev-parse --short HEAD)
98

@@ -29,11 +28,11 @@ sanity: tox pylint
2928

3029
tox:
3130
@echo -e "$(white)=$(blue) Starting sanity tox test$(reset)"
32-
tox -q
31+
$(PYTHON) -m tox -q
3332

3433
pylint:
3534
@echo -e "$(white)=$(blue) Starting sanity pylint test$(reset)"
36-
pylint lib/AddonSignals.py test
35+
$(PYTHON) -m pylint lib/AddonSignals.py test/
3736

3837
addon: clean
3938
@echo -e "$(white)=$(blue) Starting sanity addon tests$(reset)"
@@ -42,7 +41,7 @@ addon: clean
4241

4342
unit: clean
4443
@echo -e "$(white)=$(blue) Starting unit tests$(reset)"
45-
python -m unittest discover
44+
$(PYTHON) -m unittest discover
4645

4746
zip: clean
4847
@echo -e "$(white)=$(blue) Building new package$(reset)"
@@ -52,7 +51,6 @@ zip: clean
5251

5352
clean:
5453
@echo -e "$(white)=$(blue) Cleaning up$(reset)"
55-
find . -name '*.pyc' -type f -delete
56-
find . -name '*.pyo' -type f -delete
54+
find . -name '*.py[cod]' -type f -delete
5755
find . -name '__pycache__' -type d -delete
5856
rm -rf .pytest_cache/ .tox/ *.log

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[![GitHub release](https://img.shields.io/github/release/ruuk/script.module.addon.signals.svg)](https://github.com/ruuk/script.module.addon.signals/releases)
22
[![Build Status](https://travis-ci.org/ruuk/script.module.addon.signals.svg?branch=master)](https://travis-ci.org/ruuk/script.module.addon.signals)
3-
[![License: LGPL-2.1](https://img.shields.io/badge/license-lgpl__2__1-blue)](https://opensource.org/licenses/LGPL-2.1)
3+
[![License: LGPL-2.1 or later](https://img.shields.io/badge/license-LGPLv2.1_or_later-blue)](https://opensource.org/licenses/LGPL-2.1)
44
[![Contributors](https://img.shields.io/github/contributors/ruuk/script.module.addon.signals.svg)](https://github.com/ruuk/script.module.addon.signals/graphs/contributors)
55

66
# script.module.addon.signals

addon.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
<summary lang="en">Inter-addon signalling</summary>
1212
<description lang="en">Provides signal/slot mechanism for inter-addon communication</description>
1313
<platform>all</platform>
14-
<license>LGPL-2.1-only</license>
14+
<license>LGPL-2.1-or-later</license>
1515
<source>https://github.com/ruuk/script.module.addon.signals</source>
1616
</extension>
1717
</addon>

lib/AddonSignals.py

Lines changed: 60 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,135 +1,161 @@
11
# -*- coding: utf-8 -*-
2+
# GNU Lesser General Public License v2.1 (see COPYING or https://www.gnu.org/licenses/gpl-2.1.txt)
3+
''' The AddonSignals module provides signal/slot mechanism for inter-addon communication in Kodi '''
24

5+
from __future__ import absolute_import, division, unicode_literals
36
import sys
47
import base64
58
import json
69
import xbmc
710
import xbmcaddon
811

9-
RECEIVER = None
1012

13+
def _addon_id():
14+
''' Return the Kodi add-on id and cache it as a static variable '''
15+
if not hasattr(_addon_id, 'cached'):
16+
_addon_id.cached = xbmcaddon.Addon().getAddonInfo('id')
17+
return getattr(_addon_id, 'cached')
1118

12-
def _getReceiver():
13-
global RECEIVER # pylint: disable=global-statement
14-
if not RECEIVER:
15-
RECEIVER = SignalReceiver()
16-
return RECEIVER
1719

18-
19-
def _decodeData(data):
20+
def _decode_data(data):
21+
''' Decode base64-encoded JSON data and return Python data structure '''
2022
encoded_data = json.loads(data)
21-
if encoded_data:
22-
json_data = base64.b64decode(encoded_data[0])
23-
# NOTE: With Python 3.5 and older json.loads() does not support bytes or bytearray
24-
if isinstance(json_data, bytes):
25-
json_data = json_data.decode('utf-8')
26-
return json.loads(json_data)
27-
28-
return None
23+
if not encoded_data:
24+
return None
25+
json_data = base64.b64decode(encoded_data[0])
26+
# NOTE: With Python 3.5 and older json.loads() does not support bytes or bytearray
27+
return json.loads(_to_unicode(json_data))
2928

3029

31-
def _encodeData(data):
30+
def _encode_data(data):
31+
''' Encode Python data structure into base64-encoded JSON data '''
3232
json_data = json.dumps(data)
3333
if not isinstance(json_data, bytes):
3434
json_data = json_data.encode('utf-8')
3535
encoded_data = base64.b64encode(json_data)
36-
if sys.version_info[0] > 2:
37-
encoded_data = encoded_data.decode('ascii')
38-
return encoded_data
36+
return encoded_data.decode('ascii')
37+
38+
39+
def _get_receiver():
40+
''' Return a SignalReceiver instance and cache it as a static variable '''
41+
if not hasattr(_get_receiver, 'cached'):
42+
_get_receiver.cached = SignalReceiver()
43+
return getattr(_get_receiver, 'cached')
3944

4045

4146
def _jsonrpc(**kwargs):
4247
''' Perform JSONRPC calls '''
4348
if 'id' not in kwargs:
44-
kwargs.update(id=1)
49+
kwargs.update(id=0)
4550
if 'jsonrpc' not in kwargs:
4651
kwargs.update(jsonrpc='2.0')
4752
return json.loads(xbmc.executeJSONRPC(json.dumps(kwargs)))
4853

4954

55+
def _to_unicode(text, encoding='utf-8', errors='strict'):
56+
''' Force text to unicode '''
57+
if isinstance(text, bytes):
58+
return text.decode(encoding, errors=errors)
59+
return text
60+
61+
5062
class SignalReceiver(xbmc.Monitor):
63+
''' The AddonSignals receiver class '''
64+
5165
def __init__(self): # pylint: disable=super-init-not-called
66+
''' The SignalReceiver constructor '''
5267
self._slots = {}
5368

5469
def registerSlot(self, signaler_id, signal, callback):
70+
''' Register a slot in the AddonSignals receiver '''
5571
if signaler_id not in self._slots:
5672
self._slots[signaler_id] = {}
5773
self._slots[signaler_id][signal] = callback
5874

5975
def unRegisterSlot(self, signaler_id, signal):
76+
''' Unregister a slot in the AddonSignals receiver '''
6077
if signaler_id not in self._slots:
6178
return
6279
if signal not in self._slots[signaler_id]:
6380
return
6481
del self._slots[signaler_id][signal]
6582

6683
def onNotification(self, sender, method, data):
67-
if not sender[-7:] == '.SIGNAL':
84+
''' The Kodi Monitor event handler for notifications '''
85+
if not sender.endswith('.SIGNAL'):
6886
return
6987
sender = sender[:-7]
7088
if sender not in self._slots:
7189
return
7290
signal = method.split('.', 1)[-1]
7391
if signal not in self._slots[sender]:
7492
return
75-
self._slots[sender][signal](_decodeData(data))
93+
self._slots[sender][signal](_decode_data(data))
7694

7795

7896
class CallHandler:
97+
''' The AddonSignals event handler class '''
98+
7999
def __init__(self, signal, data, source_id, timeout=1000):
100+
''' The CallHandler constructor '''
80101
self.signal = signal
81-
self.data = data
82102
self.timeout = timeout
83-
self.sourceID = source_id
103+
self.source_id = source_id
84104
self._return = None
85-
registerSlot(self.sourceID, '_return.{0}'.format(self.signal), self.callback)
86-
sendSignal(signal, data, self.sourceID)
105+
registerSlot(self.source_id, '_return.{0}'.format(self.signal), self.callback)
106+
sendSignal(signal, data, self.source_id)
87107

88108
def callback(self, data):
109+
''' Method to register function as callback '''
89110
self._return = data
90111

91112
def waitForReturn(self):
113+
''' Wait for callback to trigger '''
92114
waited = 0
93115
while waited < self.timeout:
94116
if self._return is not None:
95117
break
96118
xbmc.sleep(100)
97119
waited += 100
98120

99-
unRegisterSlot(self.sourceID, self.signal)
121+
unRegisterSlot(self.source_id, self.signal)
100122

101123
return self._return
102124

103125

104126
def registerSlot(signaler_id, signal, callback):
105-
receiver = _getReceiver()
127+
''' API method to register a slot '''
128+
receiver = _get_receiver()
106129
receiver.registerSlot(signaler_id, signal, callback)
107130

108131

109132
def unRegisterSlot(signaler_id, signal):
110-
receiver = _getReceiver()
133+
''' API method to unregister a slot '''
134+
receiver = _get_receiver()
111135
receiver.unRegisterSlot(signaler_id, signal)
112136

113137

114138
def sendSignal(signal, data=None, source_id=None, sourceID=None):
139+
''' API method to send a signal '''
115140
if sourceID:
116141
xbmc.log('++++==== script.module.addon.signals: sourceID keyword is DEPRECATED - use source_id ====++++', xbmc.LOGNOTICE)
117-
source_id = source_id or sourceID or xbmcaddon.Addon().getAddonInfo('id')
118-
119142
_jsonrpc(method='JSONRPC.NotifyAll', params=dict(
120-
sender='%s.SIGNAL' % source_id,
143+
sender='%s.SIGNAL' % source_id or sourceID or _addon_id(),
121144
message=signal,
122-
data=[_encodeData(data)],
145+
data=[_encode_data(data)],
123146
))
124147

125148

126149
def registerCall(signaler_id, signal, callback):
150+
''' API method to register a callback slot '''
127151
registerSlot(signaler_id, signal, callback)
128152

129153

130154
def returnCall(signal, data=None, source_id=None):
155+
''' API method to return a callback signal '''
131156
sendSignal('_return.{0}'.format(signal), data, source_id)
132157

133158

134159
def makeCall(signal, data=None, source_id=None, timeout_ms=1000):
160+
''' API method to perform a callback signal and wait '''
135161
return CallHandler(signal, data, source_id, timeout_ms).waitForReturn()

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
codecov
12
kodi-addon-checker
23
pylint
34
setuptools >= 41

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# -*- coding: utf-8 -*-
33

44
from __future__ import absolute_import, division, unicode_literals
5-
from setuptools import setup, find_packages
5+
from setuptools import setup
66
import os
77
from xml.dom.minidom import parse
88

0 commit comments

Comments
 (0)