diff --git a/.mailmap b/.mailmap index 9de2698..606ae8a 100644 --- a/.mailmap +++ b/.mailmap @@ -8,4 +8,5 @@ Cody (Quantified Code Bot) Cody Renato.Lorenzi Ahmed Tahri TAHRI Ahmed Michael Elsdörfer Michael Elsdörfer -csoft2k \ No newline at end of file +csoft2k +Sergio Pulgarin diff --git a/AUTHORS b/AUTHORS index f5f64a5..5095c38 100644 --- a/AUTHORS +++ b/AUTHORS @@ -12,6 +12,7 @@ Hark Joel Lehtonen Kristi ldos +Lucy Linder Manuel F Martinez Michael Billington Michael Elsdörfer @@ -22,6 +23,7 @@ Qian Linfeng Renato Lorenzi Romain Porte Sam Cheng +Sergio Pulgarin Stephan Sokolow Thijs Triemstra Thomas van den Berg diff --git a/CHANGELOG.rst b/CHANGELOG.rst index eb5f2f8..7d4c0ff 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,24 @@ ********* Changelog ********* +2017-10-08 - Version 3.0a3 - "Just Testing" +------------------------------------------- +This release is the fourth alpha release of the new version 3.0. Please +be aware that the API will still change until v3.0 is released. + +changes +^^^^^^^ +- minor changes in documentation, tests and examples +- pickle capabilities for faster startup +- first implementation of centering images and QR +- check barcodes based on regex + +contributors +^^^^^^^^^^^^ +- Patrick Kanzler +- Lucy Linder +- Romain Porte +- Sergio Pulgarin 2017-08-04 - Version 3.0a2 - "It's My Party And I'll Sing If I Want To" ----------------------------------------------------------------------- diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index ad16384..4ad2108 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -20,6 +20,7 @@ When contributing the first time, please include a commit with the output of thi Otherwise the integration-check will fail. When you change your username or mail-address, please also update the `.mailmap` and the authors-list. +You can find a good documentation on the mapping-feature in the `documentation of git-shortlog `_. Style-Guide ----------- @@ -47,9 +48,11 @@ Often you can achieve compatibility quite easily with a tool from the `six`-pack PEP8 ^^^^ -This is not yet consequently done in every piece of code, but please try to ensure -that your code honors PEP8. -The checks by Landscape and QuantifiedCode that run on every PR will provide you with hints. +The entire codebase adheres to the rules of PEP8. +These rules are enforced by running `flake8` in the integration-checks. +Please adhere to these rules as your contribution can only be merged if the check succeeds. +You can use flake8 or similar tools locally in order to check your code. +Apart from that the travis-log and the check by Landscape will provide you with hints. GIT ^^^ diff --git a/README.rst b/README.rst index 31e6a3b..fc551e1 100644 --- a/README.rst +++ b/README.rst @@ -70,4 +70,13 @@ The full project-documentation is available on `Read the Docs `_ for more information. + + +Disclaimer +---------- + +None of the vendors cited in this project agree or endorse any of the patterns or implementations. +Its names are used only to maintain context. + + diff --git a/doc/generate_authors.sh b/doc/generate_authors.sh index bd770e7..aefcb6e 100755 --- a/doc/generate_authors.sh +++ b/doc/generate_authors.sh @@ -1,6 +1,6 @@ #!/bin/sh -GENLIST=$(git shortlog -s -n | cut -f2 | sort) +GENLIST=$(git shortlog -s -n | cut -f2 | sort -f) AUTHORSFILE="$(dirname $0)/../AUTHORS" TEMPAUTHORSFILE="/tmp/python-escpos-authorsfile" diff --git a/examples/codepage_tables.py b/examples/codepage_tables.py index 7966036..a071bf5 100644 --- a/examples/codepage_tables.py +++ b/examples/codepage_tables.py @@ -37,14 +37,14 @@ def print_codepage(printer, codepage): sep = "" # Table header - printer.set(text_type='B') + printer.set(font='b') printer._raw(" {}\n".format(sep.join(map(lambda s: hex(s)[2:], range(0, 16))))) printer.set() # The table for x in range(0, 16): # First column - printer.set(text_type='B') + printer.set(font='b') printer._raw("{} ".format(hex(x)[2:])) printer.set() diff --git a/examples/qr_code.py b/examples/qr_code.py index 6db8b68..325f845 100644 --- a/examples/qr_code.py +++ b/examples/qr_code.py @@ -16,4 +16,4 @@ if __name__ == '__main__': # Adapt to your needs p = Usb(0x0416, 0x5011, profile="POS-5890") - p.qr(content) + p.qr(content, center=True) diff --git a/examples/weather.py b/examples/weather.py index fe9f0af..31ef488 100644 --- a/examples/weather.py +++ b/examples/weather.py @@ -123,5 +123,5 @@ printer.set(font='a', height=2, align='center', bold=True, double_height=False) printer.text('Forecast: \n') forecast(0) forecast(1) -printer.cut +printer.cut() printer.control("LF") diff --git a/setup.py b/setup.py index f048bea..1bcc0a1 100755 --- a/setup.py +++ b/setup.py @@ -68,7 +68,6 @@ setup( url='https://github.com/python-escpos/python-escpos', download_url='https://github.com/python-escpos/python-escpos/archive/master.zip', description='Python library to manipulate ESC/POS Printers', - bugtrack_url='https://github.com/python-escpos/python-escpos/issues', license='MIT', long_description=read('README.rst'), author='Manuel F Martinez and others', @@ -114,7 +113,7 @@ setup( 'pyserial', 'six', 'appdirs', - 'pyyaml', + 'PyYAML', 'argparse', 'argcomplete', 'future', diff --git a/src/escpos/capabilities.py b/src/escpos/capabilities.py index adebf90..177029c 100644 --- a/src/escpos/capabilities.py +++ b/src/escpos/capabilities.py @@ -1,16 +1,47 @@ import re -import six from os import environ, path +import pickle +import logging +import time + +import six import yaml -# Load external printer database -if 'ESCPOS_CAPABILITIES_FILE' in environ: - file_path = environ['ESCPOS_CAPABILITIES_FILE'] -else: - file_path = path.join(path.dirname(__file__), 'capabilities.json') -with open(file_path) as f: - CAPABILITIES = yaml.load(f) +logging.basicConfig() +logger = logging.getLogger(__name__) + + +pickle_dir = environ.get('ESCPOS_CAPABILITIES_PICKLE_DIR', '/tmp/') +pickle_path = path.join(pickle_dir, 'capabilities.pickle') +capabilities_path = environ.get( + 'ESCPOS_CAPABILITIES_FILE', + path.join(path.dirname(__file__), 'capabilities.json')) + +# Load external printer database +t0 = time.time() +logger.debug('Using capabilities from file: %s', capabilities_path) +if path.exists(pickle_path): + if path.getmtime(capabilities_path) > path.getmtime(pickle_path): + logger.debug('Found a more recent capabilities file') + full_load = True + else: + full_load = False + logger.debug('Loading capabilities from pickle in %s', pickle_path) + with open(pickle_path, 'rb') as cf: + CAPABILITIES = pickle.load(cf) +else: + logger.debug('Capabilities pickle file not found: %s', pickle_path) + full_load = True + +if full_load: + logger.debug('Loading and pickling capabilities') + with open(capabilities_path) as cp, open(pickle_path, 'wb') as pp: + CAPABILITIES = yaml.load(cp) + pickle.dump(CAPABILITIES, pp, protocol=2) + +logger.debug('Finished loading capabilities took %.2fs', time.time() - t0) + PROFILES = CAPABILITIES['profiles'] diff --git a/src/escpos/constants.py b/src/escpos/constants.py index 75bdc1b..88b91f5 100644 --- a/src/escpos/constants.py +++ b/src/escpos/constants.py @@ -225,6 +225,24 @@ BARCODE_TYPE_B = { 'GS1 DATABAR EXPANDED': _SET_BARCODE_TYPE(78), } +BARCODE_FORMATS = { + 'UPC-A': ([(11, 12)], "^[0-9]{11,12}$"), + 'UPC-E': ([(7, 8), (11, 12)], "^([0-9]{7,8}|[0-9]{11,12})$"), + 'EAN13': ([(12, 13)], "^[0-9]{12,13}$"), + 'EAN8': ([(7, 8)], "^[0-9]{7,8}$"), + 'CODE39': ([(1, 255)], "^([0-9A-Z \$\%\+\-\.\/]+|\*[0-9A-Z \$\%\+\-\.\/]+\*)$"), + 'ITF': ([(2, 255)], "^([0-9]{2})+$"), + 'NW7': ([(1, 255)], "^[A-Da-d][0-9\$\+\-\.\/\:]+[A-Da-d]$"), + 'CODABAR': ([(1, 255)], "^[A-Da-d][0-9\$\+\-\.\/\:]+[A-Da-d]$"), # Same as NW7 + 'CODE93': ([(1, 255)], "^[\\x00-\\x7F]+$"), + 'CODE128': ([(2, 255)], "^\{[A-C][\\x00-\\x7F]+$"), + 'GS1-128': ([(2, 255)], "^\{[A-C][\\x00-\\x7F]+$"), # same as CODE128 + 'GS1 DATABAR OMNIDIRECTIONAL': ([(13,13)], "^[0-9]{13}$"), + 'GS1 DATABAR TRUNCATED': ([(13,13)], "^[0-9]{13}$"), # same as GS1 omnidirectional + 'GS1 DATABAR LIMITED': ([(13,13)], "^[01][0-9]{12}$"), + 'GS1 DATABAR EXPANDED': ([(2,255)], "^\([0-9][A-Za-z0-9 \!\"\%\&\'\(\)\*\+\,\-\.\/\:\;\<\=\>\?\_\{]+$"), +} + BARCODE_TYPES = { 'A': BARCODE_TYPE_A, 'B': BARCODE_TYPE_B, diff --git a/src/escpos/escpos.py b/src/escpos/escpos.py index 8d1fd81..e3b3a53 100644 --- a/src/escpos/escpos.py +++ b/src/escpos/escpos.py @@ -19,13 +19,14 @@ import qrcode import textwrap import six import time +from re import match as re_match import barcode from barcode.writer import ImageWriter from .constants import ESC, GS, NUL, QR_ECLEVEL_L, QR_ECLEVEL_M, QR_ECLEVEL_H, QR_ECLEVEL_Q from .constants import QR_MODEL_1, QR_MODEL_2, QR_MICRO, BARCODE_TYPES, BARCODE_HEIGHT, BARCODE_WIDTH -from .constants import BARCODE_FONT_A, BARCODE_FONT_B +from .constants import BARCODE_FONT_A, BARCODE_FONT_B, BARCODE_FORMATS from .constants import BARCODE_TXT_OFF, BARCODE_TXT_BTH, BARCODE_TXT_ABV, BARCODE_TXT_BLW from .constants import TXT_SIZE, TXT_NORMAL from .constants import SET_FONT @@ -80,14 +81,14 @@ class Escpos(object): """ pass - def _read(self, msg): + def _read(self): """ Returns a NotImplementedError if the instance of the class doesn't override this method. :raises NotImplementedError """ raise NotImplementedError() def image(self, img_source, high_density_vertical=True, high_density_horizontal=True, impl="bitImageRaster", - fragment_height=960): + fragment_height=960, center=False): """ Print an image You can select whether the printer should print in high density or not. The default value is high density. @@ -108,14 +109,19 @@ class Escpos(object): :param high_density_horizontal: print in high density in horizontal direction *default:* True :param impl: choose image printing mode between `bitImageRaster`, `graphics` or `bitImageColumn` :param fragment_height: Images larger than this will be split into multiple fragments *default:* 960 + :param center: Center image horizontally *default:* False """ im = EscposImage(img_source) try: max_width = int(self.profile.profile_data['media']['width']['pixels']) + if im.width > max_width: raise ImageWidthError('{} > {}'.format(im.width, max_width)) + + if center: + im.center(max_width) except KeyError: # If the printer's pixel width is not known, print anyways... pass @@ -173,7 +179,8 @@ class Escpos(object): header = self._int_low_high(len(data) + 2, 2) self._raw(GS + b'(L' + header + m + fn + data) - def qr(self, content, ec=QR_ECLEVEL_L, size=3, model=QR_MODEL_2, native=False): + def qr(self, content, ec=QR_ECLEVEL_L, size=3, model=QR_MODEL_2, + native=False, center=False): """ Print QR Code for the provided string :param content: The content of the code. Numeric data will be more efficiently compacted. @@ -185,6 +192,7 @@ class Escpos(object): by all printers). :param native: True to render the code on the printer, False to render the code as an image and send it to the printer (Default) + :param center: Centers the code *default:* False """ # Basic validation if ec not in [QR_ECLEVEL_L, QR_ECLEVEL_M, QR_ECLEVEL_H, QR_ECLEVEL_Q]: @@ -211,12 +219,17 @@ class Escpos(object): qr_code.make(fit=True) qr_img = qr_code.make_image() im = qr_img._img.convert("RGB") + # Convert the RGB image in printable image self.text('\n') - self.image(im) + self.image(im, center=center) self.text('\n') self.text('\n') return + + if center: + raise NotImplementedError("Centering not implemented for native QR rendering") + # Native 2D code printing cn = b'1' # Code type for QR code # Select model: 1, 2 or micro. @@ -275,17 +288,42 @@ class Escpos(object): else: self.magic.force_encoding(code) + @staticmethod + def check_barcode(bc, code): + """ + This method checks if the barcode is in the proper format. + The validation concerns the barcode length and the set of characters, but won't compute/validate any checksum. + The full set of requirement for each barcode type is available in the ESC/POS documentation. + + As an example, using EAN13, the barcode `12345678901` will be correct, because it can be rendered by the + printer. But it does not suit the EAN13 standard, because the checksum digit is missing. Adding a wrong + checksum in the end will also be considered correct, but adding a letter won't (EAN13 is numeric only). + + .. todo:: Add a method to compute the checksum for the different standards + + .. todo:: For fixed-length standards with mandatory checksum (EAN, UPC), + compute and add the checksum automatically if missing. + + :param bc: barcode format, see :py:func`~escpos.Escpos.barcode` + :param code: alphanumeric data to be printed as bar code, see :py:func`~escpos.Escpos.barcode` + :return: bool + """ + if bc not in BARCODE_FORMATS: + return False + + bounds, regex = BARCODE_FORMATS[bc] + return any(bound[0] <= len(code) <= bound[1] for bound in bounds) and re_match(regex, code) + def barcode(self, code, bc, height=64, width=3, pos="BELOW", font="A", - align_ct=True, function_type=None): + align_ct=True, function_type=None, check=True): """ Print Barcode This method allows to print barcodes. The rendering of the barcode is done by the printer and therefore has to - be supported by the unit. Currently you have to check manually whether your barcode text is correct. Uncorrect - barcodes may lead to unexpected printer behaviour. There are two forms of the barcode function. Type A is - default but has fewer barcodes, while type B has some more to choose from. - - .. todo:: Add a method to check barcode codes. Alternatively or as an addition write explanations about each - barcode-type. Research whether the check digits can be computed autmatically. + be supported by the unit. By default, this method will check whether your barcode text is correct, that is + the characters and lengths are supported by ESCPOS. Call the method with `check=False` to disable the check, but + note that uncorrect barcodes may lead to unexpected printer behaviour. + There are two forms of the barcode function. Type A is default but has fewer barcodes, + while type B has some more to choose from. Use the parameters `height` and `width` for adjusting of the barcode size. Please take notice that the barcode will not be printed if it is outside of the printable area. (Which should be impossible with this method, so @@ -353,6 +391,10 @@ class Escpos(object): function based on the current profile. *default*: A + :param check: If this parameter is True, the barcode format will be checked to ensure it meets the bc + requirements as defigned in the esc/pos documentation. See py:func:`~escpos.Escpos.check_barcode` + for more information. *default*: True. + :raises: :py:exc:`~escpos.exceptions.BarcodeSizeError`, :py:exc:`~escpos.exceptions.BarcodeTypeError`, :py:exc:`~escpos.exceptions.BarcodeCodeError` @@ -375,12 +417,19 @@ class Escpos(object): bc_types = BARCODE_TYPES[function_type.upper()] if bc.upper() not in bc_types.keys(): raise BarcodeTypeError(( - "Barcode type '{bc}' not valid for barcode function type " + "Barcode '{bc}' not valid for barcode function type " "{function_type}").format( bc=bc, function_type=function_type, )) + if check and not self.check_barcode(bc, code): + raise BarcodeCodeError(( + "Barcode '{code}' not in a valid format for type '{bc}'").format( + code=code, + bc=bc, + )) + # Align Bar Code() if align_ct: self._raw(TXT_STYLE['align']['center']) diff --git a/src/escpos/exceptions.py b/src/escpos/exceptions.py index 781e3e9..b82e4e0 100644 --- a/src/escpos/exceptions.py +++ b/src/escpos/exceptions.py @@ -77,9 +77,10 @@ class BarcodeSizeError(Error): class BarcodeCodeError(Error): - """ No Barcode code was supplied. + """ No Barcode code was supplied, or it is incorrect. - No data for the barcode has been supplied in :py:meth:`escpos.escpos.Escpos.barcode`. + No data for the barcode has been supplied in :py:meth:`escpos.escpos.Escpos.barcode` or the the `check` parameter + was True and the check failed. The returncode for this exception is `30`. """ def __init__(self, msg=""): diff --git a/src/escpos/image.py b/src/escpos/image.py index a5b15ab..76b75fd 100644 --- a/src/escpos/image.py +++ b/src/escpos/image.py @@ -115,3 +115,19 @@ class EscposImage(object): box = (left, upper, right, lower) fragments.append(self.img_original.crop(box)) return fragments + + def center(self, max_width): + """In-place image centering + + :param: Maximum width in order to deduce x offset for centering + :return: None + """ + old_width, height = self._im.size + new_size = (max_width, height) + + new_im = Image.new("1", new_size) + paste_x = int((max_width - old_width) / 2) + + new_im.paste(self._im, (paste_x, 0)) + + self._im = new_im diff --git a/src/escpos/printer.py b/src/escpos/printer.py index b9869f6..e51eba0 100644 --- a/src/escpos/printer.py +++ b/src/escpos/printer.py @@ -243,7 +243,7 @@ class File(Escpos): def __init__(self, devfile="/dev/usb/lp0", auto_flush=True, *args, **kwargs): """ - :param devfile : Device file under dev filesystem + :param devfile: Device file under dev filesystem :param auto_flush: automatically call flush after every call of _raw() """ Escpos.__init__(self, *args, **kwargs) diff --git a/test/test_function_barcode.py b/test/test_function_barcode.py index 710e5cd..a8f11a5 100644 --- a/test/test_function_barcode.py +++ b/test/test_function_barcode.py @@ -7,7 +7,7 @@ from __future__ import unicode_literals import escpos.printer as printer from escpos.constants import BARCODE_TYPE_A, BARCODE_TYPE_B from escpos.capabilities import Profile, BARCODE_B -from escpos.exceptions import BarcodeTypeError +from escpos.exceptions import BarcodeTypeError, BarcodeCodeError import pytest @@ -36,3 +36,17 @@ def test_lacks_support(bctype, supports_b): instance.barcode('test', bctype) assert instance.output == b'' + + +@pytest.mark.parametrize("bctype,data", [ + ('EAN13', 'AA'), + ('CODE128', '{D2354AA'), +]) +def test_code_check(bctype, data): + """should raise an error if the barcode code is invalid. + """ + instance = printer.Dummy() + with pytest.raises(BarcodeCodeError): + instance.barcode(data, bctype) + + assert instance.output == b'' diff --git a/test/test_function_check_barcode.py b/test/test_function_check_barcode.py new file mode 100644 index 0000000..e249901 --- /dev/null +++ b/test/test_function_check_barcode.py @@ -0,0 +1,104 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import escpos.printer as printer +import pytest + + +@pytest.mark.parametrize("bctype,data", [ + ('UPC-A', '01234567890'), + ('UPC-A', '012345678905'), + ('UPC-E', '01234567'), + ('UPC-E', '0123456'), + ('UPC-E', '012345678905'), + ('EAN13', '0123456789012'), + ('EAN13', '012345678901'), + ('EAN8', '01234567'), + ('EAN8', '0123456'), + ('CODE39', 'ABC-1234'), + ('CODE39', 'ABC-1234-$$-+A'), + ('CODE39', '*WIKIPEDIA*'), + ('ITF', '010203040506070809'), + ('ITF', '11221133113344556677889900'), + ('CODABAR', 'A2030405060B'), + ('CODABAR', 'C11221133113344556677889900D'), + ('CODABAR', 'D0D'), + ('NW7', 'A2030405060B'), + ('NW7', 'C11221133113344556677889900D'), + ('NW7', 'D0D'), + ('CODE93', 'A2030405060B'), + ('CODE93', '+:$&23-7@$'), + ('CODE93', 'D0D'), + ('CODE128', '{A2030405060B'), + ('CODE128', '{C+:$&23-7@$'), + ('CODE128', '{B0D'), + ('GS1-128', '{A2030405060B'), + ('GS1-128', '{C+:$&23-7@$'), + ('GS1-128', '{B0D'), + ('GS1 DATABAR OMNIDIRECTIONAL', '0123456789123'), + ('GS1 DATABAR TRUNCATED', '0123456789123'), + ('GS1 DATABAR LIMITED', '0123456789123'), + ('GS1 DATABAR EXPANDED', '(9A{A20304+-%&06a0B'), + ('GS1 DATABAR EXPANDED', '(1 {C+:&23-7%'), + ('GS1 DATABAR EXPANDED', '(00000001234567678'), +]) +def test_check_valid_barcode(bctype, data): + assert (printer.Escpos.check_barcode(bctype, data)) + + +@pytest.mark.parametrize("bctype,data", [ + ('UPC-A', '01234567890123'), # too long + ('UPC-A', '0123456789'), # too short + ('UPC-A', '72527273-711'), # invalid '-' + ('UPC-A', 'A12345678901'), # invalid 'A' + ('UPC-E', '01234567890123'), # too long + ('UPC-E', '012345'), # too short + ('UPC-E', '72527-2'), # invalid '-' + ('UPC-E', 'A123456'), # invalid 'A' + ('EAN13', '0123456789'), # too short + ('EAN13', 'A123456789012'), # invalid 'A' + ('EAN13', '012345678901234'), # too long + ('EAN8', '012345'), # too short + ('EAN8', 'A123456789012'), # invalid 'A' + ('EAN8', '012345678901234'), # too long + ('CODE39', 'ALKJ_34'), # invalid '_' + ('CODE39', 'A' * 256), # too long + ('ITF', '010203040'), # odd length + ('ITF', '0' * 256), # too long + ('ITF', 'AB01'), # invalid 'A' + ('CODABAR', '010203040'), # no start/stop + ('CODABAR', '0' * 256), # too long + ('CODABAR', 'AB-01F'), # invalid 'B' + ('NW7', '010203040'), # no start/stop + ('NW7', '0' * 256), # too long + ('NW7', 'AB-01F'), # invalid 'B' + ('CODE93', 'é010203040'), # invalid 'é' + ('CODE93', '0' * 256), # too long + ('CODE128', '010203040'), # missing leading { + ('CODE128', '{D2354AA'), # second char not between A-C + ('CODE128', '0' * 256), # too long + ('GS1-128', '010203040'), # missing leading { + ('GS1-128', '{D2354AA'), # second char not between A-C + ('GS1-128', '0' * 256), # too long + ('GS1 DATABAR OMNIDIRECTIONAL', '01234567891234'), # too long + ('GS1 DATABAR OMNIDIRECTIONAL', '012345678912'), # too short + ('GS1 DATABAR OMNIDIRECTIONAL', '012345678A1234'), # invalid 'A' + ('GS1 DATABAR TRUNCATED', '01234567891234'), # too long + ('GS1 DATABAR TRUNCATED', '012345678912'), # too short + ('GS1 DATABAR TRUNCATED', '012345678A1234'), # invalid 'A' + ('GS1 DATABAR LIMITED', '01234567891234'), # too long + ('GS1 DATABAR LIMITED', '012345678912'), # too short + ('GS1 DATABAR LIMITED', '012345678A1234'), # invalid 'A' + ('GS1 DATABAR LIMITED', '02345678912341'), # invalid start (should be 01) + ('GS1 DATABAR EXPANDED', '010203040'), # missing leading ( + ('GS1-128', '(' + ('0' * 256)), # too long + ('GS1 DATABAR EXPANDED', '(a{D2354AA'), # second char not between 0-9 + ('GS1 DATABAR EXPANDED', 'IT will fail'), # first char not '(' +]) +def test_check_invalid_barcode(bctype, data): + assert (not printer.Escpos.check_barcode(bctype, data)) diff --git a/test/test_function_image.py b/test/test_function_image.py index 5aac41b..5f66612 100644 --- a/test/test_function_image.py +++ b/test/test_function_image.py @@ -145,10 +145,8 @@ def test_large_graphics(): assert(instance.output == b'\x1dv0\x00\x01\x00\x01\x00\xc0\x1dv0\x00\x01\x00\x01\x00\x00') -def test_width_too_large(): - """ - Test printing an image that is too large in width. - """ +@pytest.fixture +def dummy_with_width(): instance = printer.Dummy() instance.profile.profile_data = { 'media': { @@ -157,8 +155,25 @@ def test_width_too_large(): } } } + return instance + + +def test_width_too_large(dummy_with_width): + """ + Test printing an image that is too large in width. + """ + instance = dummy_with_width with pytest.raises(ImageWidthError): instance.image(Image.new("RGB", (385, 200))) - instance.image(Image.new("RGB", (384, 200))) \ No newline at end of file + instance.image(Image.new("RGB", (384, 200))) + + +def test_center_image(dummy_with_width): + instance = dummy_with_width + + with pytest.raises(ImageWidthError): + instance.image(Image.new("RGB", (385, 200)), center=True) + + instance.image(Image.new("RGB", (384, 200)), center=True) diff --git a/test/test_function_qr_native.py b/test/test_function_qr_native.py index 4aa9c1d..e15e45d 100644 --- a/test/test_function_qr_native.py +++ b/test/test_function_qr_native.py @@ -13,6 +13,8 @@ from __future__ import print_function from __future__ import unicode_literals from nose.tools import raises +import pytest + import escpos.printer as printer from escpos.constants import QR_ECLEVEL_H, QR_MODEL_1 @@ -25,7 +27,6 @@ def test_defaults(): b'(k\x07\x001P01234\x1d(k\x03\x001Q0' assert(instance.output == expected) - def test_empty(): """Test QR printing blank code""" instance = printer.Dummy() @@ -99,3 +100,13 @@ def test_image_invalid_model(): """Test unsupported QR model as image""" instance = printer.Dummy() instance.qr("1234", native=False, model=QR_MODEL_1) + + +@pytest.fixture +def instance(): + return printer.Dummy() + + +def test_center_not_implementer(instance): + with pytest.raises(NotImplementedError): + instance.qr("test", center=True, native=True) \ No newline at end of file diff --git a/test/test_function_qr_non-native.py b/test/test_function_qr_non-native.py index bba47fd..a420b2b 100644 --- a/test/test_function_qr_non-native.py +++ b/test/test_function_qr_non-native.py @@ -13,6 +13,7 @@ from __future__ import division from __future__ import print_function from __future__ import unicode_literals +import pytest import mock from escpos.printer import Dummy @@ -30,3 +31,12 @@ def test_type_of_object_passed_to_image_function(img_function): d.qr("LoremIpsum") args, kwargs = img_function.call_args assert isinstance(args[0], Image.Image) + + +@pytest.fixture +def instance(): + return Dummy() + + +def test_center(instance): + instance.qr("LoremIpsum", center=True) diff --git a/test/test_printer_file.py b/test/test_printer_file.py index 48b7606..213f825 100644 --- a/test/test_printer_file.py +++ b/test/test_printer_file.py @@ -16,7 +16,7 @@ from __future__ import unicode_literals import six import pytest -from hypothesis import given +from hypothesis import given, settings from hypothesis.strategies import text import escpos.printer as printer @@ -27,6 +27,7 @@ else: mock_open_call = '__builtin__.open' +@settings(use_coverage=False) @given(path=text()) def test_load_file_printer(mocker, path): """test the loading of the file-printer""" @@ -37,6 +38,7 @@ def test_load_file_printer(mocker, path): mock_open.assert_called_with(path, "wb") +@settings(deadline=None, use_coverage=False) @given(txt=text()) def test_auto_flush(mocker, txt): """test auto_flush in file-printer""" @@ -57,6 +59,7 @@ def test_auto_flush(mocker, txt): assert mock_device.flush.called +@settings(deadline=None, use_coverage=False) @given(txt=text()) def test_flush_on_close(mocker, txt): """test flush on close in file-printer""" diff --git a/test/test_raise_arbitrary_error.py b/test/test_raise_arbitrary_error.py new file mode 100644 index 0000000..edf8fb0 --- /dev/null +++ b/test/test_raise_arbitrary_error.py @@ -0,0 +1,32 @@ +#!/usr/bin/python +"""test the raising of errors with the error module + +:author: `Patrick Kanzler `_ +:organization: `python-escpos `_ +:copyright: Copyright (c) 2017 `python-escpos `_ +:license: MIT +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +from __future__ import unicode_literals + +import pytest +import escpos +import escpos.exceptions + + +def test_raise_error_wrongly(): + """raise error the wrong way + + should reproduce https://github.com/python-escpos/python-escpos/issues/257 + """ + with pytest.raises(AttributeError): + raise escpos.Error("This should raise an AttributeError.") + + +def tests_raise_error(): + """raise error the right way""" + with pytest.raises(escpos.exceptions.Error): + raise escpos.exceptions.Error("This should raise an error.")