Skip to content

Commit

Permalink
Merge pull request #46 from kivy-garden/feature/cross_implementation_…
Browse files Browse the repository at this point in the history
…refacto_and_tests

♻️ Introduces the XZbarDecoder
  • Loading branch information
AndreMiras authored Oct 28, 2020
2 parents 16ec4a7 + cce126c commit 51e8f80
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 19 deletions.
50 changes: 34 additions & 16 deletions src/kivy_garden/zbarcam/zbarcam.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@


class ZBarDecoder:
@classmethod
def is_usable(cls):
return False

def validate_code_types(self, code_types):
available_code_types = self.get_available_code_types()

Expand Down Expand Up @@ -91,22 +95,36 @@ def decode(self, image, code_types):
]


available_implementations = {
'pyzbar': PyZBarDecoder,
'zbarlight': ZBarLightDecoder,
}
class XZbarDecoder(ZBarDecoder):
"""Proxy-like that deals with all the implementations."""
available_implementations = {
'pyzbar': PyZBarDecoder,
'zbarlight': ZBarLightDecoder,
}
zbar_decoder = None

def __init__(self):
# making it a singleton so it gets initialized once
XZbarDecoder.zbar_decoder = (
self.zbar_decoder or self._get_implementation())

def _get_implementation(self):
for name, implementation in self.available_implementations.items():
if implementation.is_usable():
zbar_decoder = implementation()
Logger.info('ZBarCam: Using implementation %s', name)
return zbar_decoder
else:
raise ImportError(
'No zbar implementation available '
f'(tried {", ".join(self.available_implementations.keys())})'
)

def get_available_code_types(self):
return self.zbar_decoder.get_available_code_types()

for name, implementation in available_implementations.items():
if implementation.is_usable():
zbar_decoder = implementation()
Logger.info('ZBarCam: Using implementation %s', name)
break
else:
raise ImportError(
'No zbar implementation available '
f'(tried {", ".join(available_implementations.keys())})'
)
def decode(self, image, code_types):
return self.zbar_decoder.decode(image, code_types)


class ZBarCam(AnchorLayout):
Expand All @@ -119,7 +137,7 @@ class ZBarCam(AnchorLayout):
symbols = ListProperty([])
Symbol = namedtuple('Symbol', ['type', 'data'])
# checking all possible types by default
code_types = ListProperty(zbar_decoder.get_available_code_types())
code_types = ListProperty(XZbarDecoder().get_available_code_types())

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

@property
def xcamera(self):
Expand Down
37 changes: 34 additions & 3 deletions tests/kivy_garden/zbarcam/test_zbarcam.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import os
import unittest
from unittest import mock

import pytest
from kivy.base import EventLoop
from kivy.core.image import Image

from kivy_garden.zbarcam import ZBarCam
from kivy_garden.zbarcam.zbarcam import XZbarDecoder

FIXTURE_DIR = os.path.join(
os.path.abspath(
Expand All @@ -16,9 +17,14 @@
EventLoop.ensure_window()


class TestZBarCam(unittest.TestCase):
def patch_is_usable(implementation, m_is_usable):
return mock.patch(
f'kivy_garden.zbarcam.zbarcam.{implementation}.is_usable', m_is_usable)

def setUp(self):

class TestZBarCam:

def setup_method(self):
with mock.patch('kivy.uix.anchorlayout.AnchorLayout.__init__'):
self.zbarcam = ZBarCam()

Expand Down Expand Up @@ -70,3 +76,28 @@ def test_detect_qrcode_frame_two_qrcodes(self):
Symbol(type='QRCODE', data=b'second zbarlight test qr code'),
Symbol(type='QRCODE', data=b'zbarlight test qr code'),
]


class TestXZbarDecoder:

def test_singleton(self):
"""
New instances of XZbarDecoder should share the same instance of
zbar_decoder.
"""
xzbar_decoder = XZbarDecoder()
zbar_decoder = xzbar_decoder.zbar_decoder
assert zbar_decoder == XZbarDecoder().zbar_decoder

def test_no_zbar_implementation_available(self):
"""
Makes sure `ImportError` is raised on no available implementations.
"""
# resets the singleton instance to force reprobing
XZbarDecoder.zbar_decoder = None
m_is_usable = mock.Mock(return_value=False)
with patch_is_usable("PyZBarDecoder", m_is_usable), \
patch_is_usable("ZBarLightDecoder", m_is_usable), \
pytest.raises(
ImportError, match="No zbar implementation available"):
XZbarDecoder()

0 comments on commit 51e8f80

Please sign in to comment.