From cce126cbd8b6313e77c6972321fe19cefe0c83c6 Mon Sep 17 00:00:00 2001 From: Andre Miras Date: Wed, 28 Oct 2020 21:01:25 +0100 Subject: [PATCH] :recycle: Introduces the XZbarDecoder :white_check_mark: Also adds tests This is a follow up for #42 --- src/kivy_garden/zbarcam/zbarcam.py | 50 +++++++++++++++-------- tests/kivy_garden/zbarcam/test_zbarcam.py | 37 +++++++++++++++-- 2 files changed, 68 insertions(+), 19 deletions(-) diff --git a/src/kivy_garden/zbarcam/zbarcam.py b/src/kivy_garden/zbarcam/zbarcam.py index d343e47..71de7cc 100644 --- a/src/kivy_garden/zbarcam/zbarcam.py +++ b/src/kivy_garden/zbarcam/zbarcam.py @@ -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() @@ -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): @@ -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, @@ -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): diff --git a/tests/kivy_garden/zbarcam/test_zbarcam.py b/tests/kivy_garden/zbarcam/test_zbarcam.py index 414894b..a0b3218 100644 --- a/tests/kivy_garden/zbarcam/test_zbarcam.py +++ b/tests/kivy_garden/zbarcam/test_zbarcam.py @@ -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( @@ -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() @@ -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()