Skip to content

Commit cce126c

Browse files
committed
♻️ Introduces the XZbarDecoder
✅ Also adds tests This is a follow up for #42
1 parent 16ec4a7 commit cce126c

File tree

2 files changed

+68
-19
lines changed

2 files changed

+68
-19
lines changed

src/kivy_garden/zbarcam/zbarcam.py

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414

1515

1616
class ZBarDecoder:
17+
@classmethod
18+
def is_usable(cls):
19+
return False
20+
1721
def validate_code_types(self, code_types):
1822
available_code_types = self.get_available_code_types()
1923

@@ -91,22 +95,36 @@ def decode(self, image, code_types):
9195
]
9296

9397

94-
available_implementations = {
95-
'pyzbar': PyZBarDecoder,
96-
'zbarlight': ZBarLightDecoder,
97-
}
98+
class XZbarDecoder(ZBarDecoder):
99+
"""Proxy-like that deals with all the implementations."""
100+
available_implementations = {
101+
'pyzbar': PyZBarDecoder,
102+
'zbarlight': ZBarLightDecoder,
103+
}
104+
zbar_decoder = None
105+
106+
def __init__(self):
107+
# making it a singleton so it gets initialized once
108+
XZbarDecoder.zbar_decoder = (
109+
self.zbar_decoder or self._get_implementation())
110+
111+
def _get_implementation(self):
112+
for name, implementation in self.available_implementations.items():
113+
if implementation.is_usable():
114+
zbar_decoder = implementation()
115+
Logger.info('ZBarCam: Using implementation %s', name)
116+
return zbar_decoder
117+
else:
118+
raise ImportError(
119+
'No zbar implementation available '
120+
f'(tried {", ".join(self.available_implementations.keys())})'
121+
)
98122

123+
def get_available_code_types(self):
124+
return self.zbar_decoder.get_available_code_types()
99125

100-
for name, implementation in available_implementations.items():
101-
if implementation.is_usable():
102-
zbar_decoder = implementation()
103-
Logger.info('ZBarCam: Using implementation %s', name)
104-
break
105-
else:
106-
raise ImportError(
107-
'No zbar implementation available '
108-
f'(tried {", ".join(available_implementations.keys())})'
109-
)
126+
def decode(self, image, code_types):
127+
return self.zbar_decoder.decode(image, code_types)
110128

111129

112130
class ZBarCam(AnchorLayout):
@@ -119,7 +137,7 @@ class ZBarCam(AnchorLayout):
119137
symbols = ListProperty([])
120138
Symbol = namedtuple('Symbol', ['type', 'data'])
121139
# checking all possible types by default
122-
code_types = ListProperty(zbar_decoder.get_available_code_types())
140+
code_types = ListProperty(XZbarDecoder().get_available_code_types())
123141

124142
def __init__(self, **kwargs):
125143
# lazy loading the kv file rather than loading at module level,
@@ -170,7 +188,7 @@ def _detect_qrcode_frame(cls, texture, code_types):
170188
pil_image = PIL.Image.frombytes(mode='RGBA', size=size,
171189
data=image_data)
172190
pil_image = fix_android_image(pil_image)
173-
return zbar_decoder.decode(pil_image, code_types)
191+
return XZbarDecoder().decode(pil_image, code_types)
174192

175193
@property
176194
def xcamera(self):

tests/kivy_garden/zbarcam/test_zbarcam.py

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import os
2-
import unittest
32
from unittest import mock
43

4+
import pytest
55
from kivy.base import EventLoop
66
from kivy.core.image import Image
77

88
from kivy_garden.zbarcam import ZBarCam
9+
from kivy_garden.zbarcam.zbarcam import XZbarDecoder
910

1011
FIXTURE_DIR = os.path.join(
1112
os.path.abspath(
@@ -16,9 +17,14 @@
1617
EventLoop.ensure_window()
1718

1819

19-
class TestZBarCam(unittest.TestCase):
20+
def patch_is_usable(implementation, m_is_usable):
21+
return mock.patch(
22+
f'kivy_garden.zbarcam.zbarcam.{implementation}.is_usable', m_is_usable)
2023

21-
def setUp(self):
24+
25+
class TestZBarCam:
26+
27+
def setup_method(self):
2228
with mock.patch('kivy.uix.anchorlayout.AnchorLayout.__init__'):
2329
self.zbarcam = ZBarCam()
2430

@@ -70,3 +76,28 @@ def test_detect_qrcode_frame_two_qrcodes(self):
7076
Symbol(type='QRCODE', data=b'second zbarlight test qr code'),
7177
Symbol(type='QRCODE', data=b'zbarlight test qr code'),
7278
]
79+
80+
81+
class TestXZbarDecoder:
82+
83+
def test_singleton(self):
84+
"""
85+
New instances of XZbarDecoder should share the same instance of
86+
zbar_decoder.
87+
"""
88+
xzbar_decoder = XZbarDecoder()
89+
zbar_decoder = xzbar_decoder.zbar_decoder
90+
assert zbar_decoder == XZbarDecoder().zbar_decoder
91+
92+
def test_no_zbar_implementation_available(self):
93+
"""
94+
Makes sure `ImportError` is raised on no available implementations.
95+
"""
96+
# resets the singleton instance to force reprobing
97+
XZbarDecoder.zbar_decoder = None
98+
m_is_usable = mock.Mock(return_value=False)
99+
with patch_is_usable("PyZBarDecoder", m_is_usable), \
100+
patch_is_usable("ZBarLightDecoder", m_is_usable), \
101+
pytest.raises(
102+
ImportError, match="No zbar implementation available"):
103+
XZbarDecoder()

0 commit comments

Comments
 (0)