Merge pull request #266 from python-escpos/development

release v3.0a3
This commit is contained in:
Patrick Kanzler 2017-10-08 22:45:54 +02:00 committed by GitHub
commit d1e7052fa1
23 changed files with 380 additions and 44 deletions

View File

@ -8,4 +8,5 @@ Cody (Quantified Code Bot) <cody@quantifiedcode.com> Cody <cody@quantifiedcode.c
Renato Lorenzi <renato.lorenzi@senior.com.br> Renato.Lorenzi <renato.lorenzi@senior.com.br> Renato Lorenzi <renato.lorenzi@senior.com.br> Renato.Lorenzi <renato.lorenzi@senior.com.br>
Ahmed Tahri <nyuubi.10@gmail.com> TAHRI Ahmed <nyuubi.10@gmail.com> Ahmed Tahri <nyuubi.10@gmail.com> TAHRI Ahmed <nyuubi.10@gmail.com>
Michael Elsdörfer <michael@elsdoerfer.com> Michael Elsdörfer <michael@elsdoerfer.info> Michael Elsdörfer <michael@elsdoerfer.com> Michael Elsdörfer <michael@elsdoerfer.info>
csoft2k <csoft2k@hotmail.com> csoft2k <csoft2k@hotmail.com>
Sergio Pulgarin <sergio.pulgarin@gmail.com>

View File

@ -12,6 +12,7 @@ Hark
Joel Lehtonen Joel Lehtonen
Kristi Kristi
ldos ldos
Lucy Linder
Manuel F Martinez Manuel F Martinez
Michael Billington Michael Billington
Michael Elsdörfer Michael Elsdörfer
@ -22,6 +23,7 @@ Qian Linfeng
Renato Lorenzi Renato Lorenzi
Romain Porte Romain Porte
Sam Cheng Sam Cheng
Sergio Pulgarin
Stephan Sokolow Stephan Sokolow
Thijs Triemstra Thijs Triemstra
Thomas van den Berg Thomas van den Berg

View File

@ -1,6 +1,24 @@
********* *********
Changelog 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" 2017-08-04 - Version 3.0a2 - "It's My Party And I'll Sing If I Want To"
----------------------------------------------------------------------- -----------------------------------------------------------------------

View File

@ -20,6 +20,7 @@ When contributing the first time, please include a commit with the output of thi
Otherwise the integration-check will fail. Otherwise the integration-check will fail.
When you change your username or mail-address, please also update the `.mailmap` and the authors-list. 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 <https://git-scm.com/docs/git-shortlog#_mapping_authors>`_.
Style-Guide Style-Guide
----------- -----------
@ -47,9 +48,11 @@ Often you can achieve compatibility quite easily with a tool from the `six`-pack
PEP8 PEP8
^^^^ ^^^^
This is not yet consequently done in every piece of code, but please try to ensure The entire codebase adheres to the rules of PEP8.
that your code honors PEP8. These rules are enforced by running `flake8` in the integration-checks.
The checks by Landscape and QuantifiedCode that run on every PR will provide you with hints. 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 GIT
^^^ ^^^

View File

@ -70,4 +70,13 @@ The full project-documentation is available on `Read the Docs <https://python-es
Contributing Contributing
------------ ------------
This project is open for any contribution! Please see CONTRIBUTING.rst for more information. This project is open for any contribution! Please see `CONTRIBUTING.rst <http://python-escpos.readthedocs.io/en/latest/dev/contributing.html>`_ 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.

View File

@ -1,6 +1,6 @@
#!/bin/sh #!/bin/sh
GENLIST=$(git shortlog -s -n | cut -f2 | sort) GENLIST=$(git shortlog -s -n | cut -f2 | sort -f)
AUTHORSFILE="$(dirname $0)/../AUTHORS" AUTHORSFILE="$(dirname $0)/../AUTHORS"
TEMPAUTHORSFILE="/tmp/python-escpos-authorsfile" TEMPAUTHORSFILE="/tmp/python-escpos-authorsfile"

View File

@ -37,14 +37,14 @@ def print_codepage(printer, codepage):
sep = "" sep = ""
# Table header # 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._raw(" {}\n".format(sep.join(map(lambda s: hex(s)[2:], range(0, 16)))))
printer.set() printer.set()
# The table # The table
for x in range(0, 16): for x in range(0, 16):
# First column # First column
printer.set(text_type='B') printer.set(font='b')
printer._raw("{} ".format(hex(x)[2:])) printer._raw("{} ".format(hex(x)[2:]))
printer.set() printer.set()

View File

@ -16,4 +16,4 @@ if __name__ == '__main__':
# Adapt to your needs # Adapt to your needs
p = Usb(0x0416, 0x5011, profile="POS-5890") p = Usb(0x0416, 0x5011, profile="POS-5890")
p.qr(content) p.qr(content, center=True)

View File

@ -123,5 +123,5 @@ printer.set(font='a', height=2, align='center', bold=True, double_height=False)
printer.text('Forecast: \n') printer.text('Forecast: \n')
forecast(0) forecast(0)
forecast(1) forecast(1)
printer.cut printer.cut()
printer.control("LF") printer.control("LF")

View File

@ -68,7 +68,6 @@ setup(
url='https://github.com/python-escpos/python-escpos', url='https://github.com/python-escpos/python-escpos',
download_url='https://github.com/python-escpos/python-escpos/archive/master.zip', download_url='https://github.com/python-escpos/python-escpos/archive/master.zip',
description='Python library to manipulate ESC/POS Printers', description='Python library to manipulate ESC/POS Printers',
bugtrack_url='https://github.com/python-escpos/python-escpos/issues',
license='MIT', license='MIT',
long_description=read('README.rst'), long_description=read('README.rst'),
author='Manuel F Martinez and others', author='Manuel F Martinez and others',
@ -114,7 +113,7 @@ setup(
'pyserial', 'pyserial',
'six', 'six',
'appdirs', 'appdirs',
'pyyaml', 'PyYAML',
'argparse', 'argparse',
'argcomplete', 'argcomplete',
'future', 'future',

View File

@ -1,16 +1,47 @@
import re import re
import six
from os import environ, path from os import environ, path
import pickle
import logging
import time
import six
import yaml 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: logging.basicConfig()
CAPABILITIES = yaml.load(f) 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'] PROFILES = CAPABILITIES['profiles']

View File

@ -225,6 +225,24 @@ BARCODE_TYPE_B = {
'GS1 DATABAR EXPANDED': _SET_BARCODE_TYPE(78), '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 = { BARCODE_TYPES = {
'A': BARCODE_TYPE_A, 'A': BARCODE_TYPE_A,
'B': BARCODE_TYPE_B, 'B': BARCODE_TYPE_B,

View File

@ -19,13 +19,14 @@ import qrcode
import textwrap import textwrap
import six import six
import time import time
from re import match as re_match
import barcode import barcode
from barcode.writer import ImageWriter 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 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 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 BARCODE_TXT_OFF, BARCODE_TXT_BTH, BARCODE_TXT_ABV, BARCODE_TXT_BLW
from .constants import TXT_SIZE, TXT_NORMAL from .constants import TXT_SIZE, TXT_NORMAL
from .constants import SET_FONT from .constants import SET_FONT
@ -80,14 +81,14 @@ class Escpos(object):
""" """
pass pass
def _read(self, msg): def _read(self):
""" Returns a NotImplementedError if the instance of the class doesn't override this method. """ Returns a NotImplementedError if the instance of the class doesn't override this method.
:raises NotImplementedError :raises NotImplementedError
""" """
raise NotImplementedError() raise NotImplementedError()
def image(self, img_source, high_density_vertical=True, high_density_horizontal=True, impl="bitImageRaster", 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 """ Print an image
You can select whether the printer should print in high density or not. The default value is high density. 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 high_density_horizontal: print in high density in horizontal direction *default:* True
:param impl: choose image printing mode between `bitImageRaster`, `graphics` or `bitImageColumn` :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 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) im = EscposImage(img_source)
try: try:
max_width = int(self.profile.profile_data['media']['width']['pixels']) max_width = int(self.profile.profile_data['media']['width']['pixels'])
if im.width > max_width: if im.width > max_width:
raise ImageWidthError('{} > {}'.format(im.width, max_width)) raise ImageWidthError('{} > {}'.format(im.width, max_width))
if center:
im.center(max_width)
except KeyError: except KeyError:
# If the printer's pixel width is not known, print anyways... # If the printer's pixel width is not known, print anyways...
pass pass
@ -173,7 +179,8 @@ class Escpos(object):
header = self._int_low_high(len(data) + 2, 2) header = self._int_low_high(len(data) + 2, 2)
self._raw(GS + b'(L' + header + m + fn + data) 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 """ Print QR Code for the provided string
:param content: The content of the code. Numeric data will be more efficiently compacted. :param content: The content of the code. Numeric data will be more efficiently compacted.
@ -185,6 +192,7 @@ class Escpos(object):
by all printers). 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 :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) printer (Default)
:param center: Centers the code *default:* False
""" """
# Basic validation # Basic validation
if ec not in [QR_ECLEVEL_L, QR_ECLEVEL_M, QR_ECLEVEL_H, QR_ECLEVEL_Q]: 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_code.make(fit=True)
qr_img = qr_code.make_image() qr_img = qr_code.make_image()
im = qr_img._img.convert("RGB") im = qr_img._img.convert("RGB")
# Convert the RGB image in printable image # Convert the RGB image in printable image
self.text('\n') self.text('\n')
self.image(im) self.image(im, center=center)
self.text('\n') self.text('\n')
self.text('\n') self.text('\n')
return return
if center:
raise NotImplementedError("Centering not implemented for native QR rendering")
# Native 2D code printing # Native 2D code printing
cn = b'1' # Code type for QR code cn = b'1' # Code type for QR code
# Select model: 1, 2 or micro. # Select model: 1, 2 or micro.
@ -275,17 +288,42 @@ class Escpos(object):
else: else:
self.magic.force_encoding(code) 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", 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 """ Print Barcode
This method allows to print barcodes. The rendering of the barcode is done by the printer and therefore has to 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 be supported by the unit. By default, this method will check whether your barcode text is correct, that is
barcodes may lead to unexpected printer behaviour. There are two forms of the barcode function. Type A is the characters and lengths are supported by ESCPOS. Call the method with `check=False` to disable the check, but
default but has fewer barcodes, while type B has some more to choose from. 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,
.. todo:: Add a method to check barcode codes. Alternatively or as an addition write explanations about each while type B has some more to choose from.
barcode-type. Research whether the check digits can be computed autmatically.
Use the parameters `height` and `width` for adjusting of the barcode size. Please take notice that the barcode 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 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. function based on the current profile.
*default*: A *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`, :raises: :py:exc:`~escpos.exceptions.BarcodeSizeError`,
:py:exc:`~escpos.exceptions.BarcodeTypeError`, :py:exc:`~escpos.exceptions.BarcodeTypeError`,
:py:exc:`~escpos.exceptions.BarcodeCodeError` :py:exc:`~escpos.exceptions.BarcodeCodeError`
@ -375,12 +417,19 @@ class Escpos(object):
bc_types = BARCODE_TYPES[function_type.upper()] bc_types = BARCODE_TYPES[function_type.upper()]
if bc.upper() not in bc_types.keys(): if bc.upper() not in bc_types.keys():
raise BarcodeTypeError(( raise BarcodeTypeError((
"Barcode type '{bc}' not valid for barcode function type " "Barcode '{bc}' not valid for barcode function type "
"{function_type}").format( "{function_type}").format(
bc=bc, bc=bc,
function_type=function_type, 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() # Align Bar Code()
if align_ct: if align_ct:
self._raw(TXT_STYLE['align']['center']) self._raw(TXT_STYLE['align']['center'])

View File

@ -77,9 +77,10 @@ class BarcodeSizeError(Error):
class BarcodeCodeError(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`. The returncode for this exception is `30`.
""" """
def __init__(self, msg=""): def __init__(self, msg=""):

View File

@ -115,3 +115,19 @@ class EscposImage(object):
box = (left, upper, right, lower) box = (left, upper, right, lower)
fragments.append(self.img_original.crop(box)) fragments.append(self.img_original.crop(box))
return fragments 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

View File

@ -243,7 +243,7 @@ class File(Escpos):
def __init__(self, devfile="/dev/usb/lp0", auto_flush=True, *args, **kwargs): 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() :param auto_flush: automatically call flush after every call of _raw()
""" """
Escpos.__init__(self, *args, **kwargs) Escpos.__init__(self, *args, **kwargs)

View File

@ -7,7 +7,7 @@ from __future__ import unicode_literals
import escpos.printer as printer import escpos.printer as printer
from escpos.constants import BARCODE_TYPE_A, BARCODE_TYPE_B from escpos.constants import BARCODE_TYPE_A, BARCODE_TYPE_B
from escpos.capabilities import Profile, BARCODE_B from escpos.capabilities import Profile, BARCODE_B
from escpos.exceptions import BarcodeTypeError from escpos.exceptions import BarcodeTypeError, BarcodeCodeError
import pytest import pytest
@ -36,3 +36,17 @@ def test_lacks_support(bctype, supports_b):
instance.barcode('test', bctype) instance.barcode('test', bctype)
assert instance.output == b'' 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''

View File

@ -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))

View File

@ -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') assert(instance.output == b'\x1dv0\x00\x01\x00\x01\x00\xc0\x1dv0\x00\x01\x00\x01\x00\x00')
def test_width_too_large(): @pytest.fixture
""" def dummy_with_width():
Test printing an image that is too large in width.
"""
instance = printer.Dummy() instance = printer.Dummy()
instance.profile.profile_data = { instance.profile.profile_data = {
'media': { '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): with pytest.raises(ImageWidthError):
instance.image(Image.new("RGB", (385, 200))) instance.image(Image.new("RGB", (385, 200)))
instance.image(Image.new("RGB", (384, 200))) 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)

View File

@ -13,6 +13,8 @@ from __future__ import print_function
from __future__ import unicode_literals from __future__ import unicode_literals
from nose.tools import raises from nose.tools import raises
import pytest
import escpos.printer as printer import escpos.printer as printer
from escpos.constants import QR_ECLEVEL_H, QR_MODEL_1 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' b'(k\x07\x001P01234\x1d(k\x03\x001Q0'
assert(instance.output == expected) assert(instance.output == expected)
def test_empty(): def test_empty():
"""Test QR printing blank code""" """Test QR printing blank code"""
instance = printer.Dummy() instance = printer.Dummy()
@ -99,3 +100,13 @@ def test_image_invalid_model():
"""Test unsupported QR model as image""" """Test unsupported QR model as image"""
instance = printer.Dummy() instance = printer.Dummy()
instance.qr("1234", native=False, model=QR_MODEL_1) 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)

View File

@ -13,6 +13,7 @@ from __future__ import division
from __future__ import print_function from __future__ import print_function
from __future__ import unicode_literals from __future__ import unicode_literals
import pytest
import mock import mock
from escpos.printer import Dummy from escpos.printer import Dummy
@ -30,3 +31,12 @@ def test_type_of_object_passed_to_image_function(img_function):
d.qr("LoremIpsum") d.qr("LoremIpsum")
args, kwargs = img_function.call_args args, kwargs = img_function.call_args
assert isinstance(args[0], Image.Image) assert isinstance(args[0], Image.Image)
@pytest.fixture
def instance():
return Dummy()
def test_center(instance):
instance.qr("LoremIpsum", center=True)

View File

@ -16,7 +16,7 @@ from __future__ import unicode_literals
import six import six
import pytest import pytest
from hypothesis import given from hypothesis import given, settings
from hypothesis.strategies import text from hypothesis.strategies import text
import escpos.printer as printer import escpos.printer as printer
@ -27,6 +27,7 @@ else:
mock_open_call = '__builtin__.open' mock_open_call = '__builtin__.open'
@settings(use_coverage=False)
@given(path=text()) @given(path=text())
def test_load_file_printer(mocker, path): def test_load_file_printer(mocker, path):
"""test the loading of the file-printer""" """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") mock_open.assert_called_with(path, "wb")
@settings(deadline=None, use_coverage=False)
@given(txt=text()) @given(txt=text())
def test_auto_flush(mocker, txt): def test_auto_flush(mocker, txt):
"""test auto_flush in file-printer""" """test auto_flush in file-printer"""
@ -57,6 +59,7 @@ def test_auto_flush(mocker, txt):
assert mock_device.flush.called assert mock_device.flush.called
@settings(deadline=None, use_coverage=False)
@given(txt=text()) @given(txt=text())
def test_flush_on_close(mocker, txt): def test_flush_on_close(mocker, txt):
"""test flush on close in file-printer""" """test flush on close in file-printer"""

View File

@ -0,0 +1,32 @@
#!/usr/bin/python
"""test the raising of errors with the error module
:author: `Patrick Kanzler <patrick.kanzler@fablab.fau.de>`_
:organization: `python-escpos <https://github.com/python-escpos>`_
:copyright: Copyright (c) 2017 `python-escpos <https://github.com/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.")