Merge branch 'capabilities' into text-encoding

This commit is contained in:
Michael Elsdörfer 2016-08-30 13:36:53 +02:00
commit b37f4fc8cc
13 changed files with 277 additions and 276 deletions

View File

@ -15,6 +15,21 @@ contributors
- Patrick Kanzler (with code by Frédéric Van der Essen) - Patrick Kanzler (with code by Frédéric Van der Essen)
2016-08-26 - Version 2.2.0 - "Fate Amenable To Change"
------------------------------------------------------
changes
^^^^^^^
- fix improper API-use in qrcode()
- change setup.py shebang to make it compatible with virtualenvs.
- add constants for sheet mode and colors
- support changing the linespacing
contributors
^^^^^^^^^^^^
- Michael Elsdörfer
- Patrick Kanzler
2016-08-10 - Version 2.1.3 - "Ethics Gradient" 2016-08-10 - Version 2.1.3 - "Ethics Gradient"
---------------------------------------------- ----------------------------------------------

View File

@ -54,14 +54,14 @@ The basic usage is:
.. code:: python .. code:: python
from escpos import * from escpos.printer import Usb
""" Seiko Epson Corp. Receipt Printer M129 Definitions (EPSON TM-T88IV) """ """ Seiko Epson Corp. Receipt Printer M129 Definitions (EPSON TM-T88IV) """
Epson = escpos.Escpos(0x04b8,0x0202,0) p = Usb(0x04b8,0x0202,0)
Epson.text("Hello World\n") p.text("Hello World\n")
Epson.image("logo.gif") p.image("logo.gif")
Epson.barcode('1324354657687','EAN13',64,2,'','') p.barcode('1324354657687','EAN13',64,2,'','')
Epson.cut() p.cut()
The full project-documentation is available on `Read the Docs <https://python-escpos.readthedocs.io>`_. The full project-documentation is available on `Read the Docs <https://python-escpos.readthedocs.io>`_.

View File

@ -1,4 +1,4 @@
#!/usr/bin/python #!/usr/bin/env python
import os import os
import sys import sys
@ -83,7 +83,8 @@ setup(
platforms='any', platforms='any',
package_dir={"": "src"}, package_dir={"": "src"},
packages=find_packages(where="src", exclude=["tests", "tests.*"]), packages=find_packages(where="src", exclude=["tests", "tests.*"]),
package_data={'': ['COPYING']}, package_data={'': ['COPYING', 'src/escpos/capabilities.json']},
include_package_data=True,
classifiers=[ classifiers=[
'Development Status :: 4 - Beta', 'Development Status :: 4 - Beta',
'Environment :: Console', 'Environment :: Console',
@ -116,7 +117,7 @@ setup(
setup_requires=[ setup_requires=[
'setuptools_scm', 'setuptools_scm',
], ],
tests_require=['tox', 'nose', 'scripttest', 'mock', 'hypothesis'], tests_require=['tox', 'pytest', 'pytest-cov', 'nose', 'scripttest', 'mock', 'hypothesis'],
cmdclass={'test': Tox}, cmdclass={'test': Tox},
entry_points={ entry_points={
'console_scripts': [ 'console_scripts': [

File diff suppressed because one or more lines are too long

View File

@ -1,40 +1,61 @@
import re import re
import six
from os import path from os import path
import yaml import yaml
with open(path.join(path.dirname(__file__), 'capabilities.yml')) as f: # Load external printer database
PROFILES = yaml.load(f) with open(path.join(path.dirname(__file__), 'capabilities.json')) as f:
CAPABILITIES = yaml.load(f)
PROFILES = CAPABILITIES['profiles']
ENCODINGS = CAPABILITIES['encodings']
class Profile(object): class NotSupported(Exception):
pass
BARCODE_B = 'barcodeB'
class BaseProfile(object):
"""This respresents a printer profile.
A printer profile knows about the number of columns, supported
features, colors and more.
"""
profile_data = {} profile_data = {}
def __init__(self, columns=None):
self.default_columns = columns
def __getattr__(self, name): def __getattr__(self, name):
return self.profile_data[name] return self.profile_data[name]
def get_font(self, font):
"""Return the escpos index for `font`. Makes sure that
the requested `font` is valid.
"""
font = {'a': 0, 'b': 1}.get(font, font)
if not six.text_type(font) in self.fonts:
raise NotSupported(
'"%s" is not a valid font in the current profile' % font)
return font
def get_columns(self, font): def get_columns(self, font):
""" Return the number of columns for the given font. """ Return the number of columns for the given font.
""" """
if self.default_columns: font = self.get_font(font)
return self.default_columns return self.fonts[six.text_type(font)]['columns']
if 'columnConfigs' in self.profile_data: def supports(self, feature):
columns_def = self.columnConfigs[self.defaultColumnConfig] """Return true/false for the given feature.
"""
elif 'columns' in self.profile_data: return self.features.get(feature)
columns_def = self.columns
if isinstance(columns_def, int):
return columns_def
return columns_def[font]
def get_profile(name=None, **kwargs): def get_profile(name=None, **kwargs):
"""Get the profile by name; if no name is given, return the
default profile.
"""
if isinstance(name, Profile): if isinstance(name, Profile):
return name return name
@ -42,15 +63,19 @@ def get_profile(name=None, **kwargs):
return clazz(**kwargs) return clazz(**kwargs)
CLASS_CACHE = {} CLASS_CACHE = {}
def get_profile_class(name): def get_profile_class(name):
"""For the given profile name, load the data from the external
database, then generate dynamically a class.
"""
if not name in CLASS_CACHE: if not name in CLASS_CACHE:
profile_data = resolve_profile_data(name) profile_data = PROFILES[name]
class_name = '%sProfile' % clean(name) profile_name = clean(name)
new_class = type(class_name, (Profile,), {'profile_data': profile_data}) class_name = '{}{}Profile'.format(
profile_name[0].upper(), profile_name[1:])
new_class = type(class_name, (BaseProfile,), {'profile_data': profile_data})
CLASS_CACHE[name] = new_class CLASS_CACHE[name] = new_class
return CLASS_CACHE[name] return CLASS_CACHE[name]
@ -64,20 +89,21 @@ def clean(s):
return str(s) return str(s)
def resolve_profile_data(name): # For users, who want to provide their profile
data = PROFILES[name] class Profile(get_profile_class('default')):
inherits = data.get('inherits')
if not inherits: def __init__(self, columns=None, features={}):
return data super(Profile, self).__init__()
self.columns = columns
self.features = features
def get_columns(self, font):
if self.columns is not None:
return self.columns
return super(Profile, self).get_columns(font)
if not isinstance(inherits, (tuple, list)):
inherits = [inherits]
merged = {}
for base in reversed(inherits):
base_data = resolve_profile_data(base)
merged.update(base_data)
merged.update(data)
return merged

View File

@ -1,207 +0,0 @@
# Description of the format
abstract:
# Defines non-standard code pages that the printer supports, but
# that we won't find in Python's encoding system. If you define one
# here, don't forget to add it to codePageMap to assign it to a slot.
customCodePages:
sample:
# This maps the indexed code page slots to code page names.
# Often, the slot assignment is the same, but the device only
# supports a subset.
codePageMap:
0: "CP437"
1: "CP932"
3: "sample"
# Maybe not all of the codepages in the map are supported. This
# is for subprofiles to select which ones the device knows.
codePages: [sample, cp932]
# Many recent Epson-branded thermal receipt printers.
default:
columns: 42
barcodeB: true
bitImage: true
graphics: true
starCommands: false
qrCode: true
customCodePages:
TCVN-3-1: [
" ",
" ",
" ăâêôơưđ ",
" àảãáạ ằẳẵắ ",
" ặầẩẫấậè ẻẽ",
"éẹềểễếệìỉ ĩíịò",
" ỏõóọồổỗốộờởỡớợù",
" ủũúụừửữứựỳỷỹýỵ ",
]
TCVN-3-2: [
" ",
" ",
" ĂÂ Ð ÊÔƠƯ ",
" ÀẢÃÁẠ ẰẲẴẮ ",
" ẶẦẨẪẤẬÈ ẺẼ",
"ÉẸỀỂỄẾỆÌỈ ĨÍỊÒ",
" ỎÕÓỌỒỔỖỐỘỜỞỠỚỢÙ",
" ỦŨÚỤỪỬỮỨỰỲỶỸÝỴ "
]
# Commented-out slots are TODO (might just need uncomment, might
# need verification/research)
codePageMap:
0: "CP437"
1: "CP932"
2: "CP850"
3: "CP860"
4: "CP863"
5: "CP865"
#6: // Hiragana
#7: // One-pass printing Kanji characters
#8: // Page 8 [One-pass printing Kanji characters]
11: "CP851"
12: "CP853"
13: "CP857"
14: "CP737"
15: "ISO8859_7"
16: "CP1252"
17: "CP866"
18: "CP852"
19: "CP858"
#20: // Thai Character Code 42
#21: // Thai Character Code 1"
#22: // Thai Character Code 13
#23: // Thai Character Code 14
#24: // Thai Character Code 16
#25: // Thai Character Code 17
#26: // Thai Character Code 18
30: 'TCVN-3-1' # TCVN-3: Vietnamese
31: 'TCVN-3-2' # TCVN-3: Vietnamese
32: "CP720"
33: "CP775"
34: "CP855"
35: "CP861"
36: "CP862"
37: "CP864"
38: "CP869"
39: "ISO8859_2"
40: "ISO8859_15"
41: "CP1098"
42: "CP774"
43: "CP772"
44: "CP1125"
45: "CP1250"
46: "CP1251"
47: "CP1253"
48: "CP1254"
49: "CP1255"
50: "CP1256"
51: "CP1257"
52: "CP1258"
53: "RK1048"
#66: // Devanagari
#67: // Bengali
#68: // Tamil
#69: // Telugu
#70: // Assamese
#71: // Oriya
#72: // Kannada
#73: // Malayalam
#74: // Gujarati
#75: // Punjabi
#82: // Marathi
#254:
#255:
# Designed for non-Epson printers sold online. Without knowing
# their character encoding table, only CP437 output is assumed,
# and graphics() calls will be disabled, as it usually prints junk
# on these models.
simple:
codePages:
- cp437
graphics: false
# Profile for Star-branded printers.
star:
inherits: default
starCommands: true
epson:
inherits: default
manufacturer: "Epson"
"P-822D":
inherits: default
graphics: false
# http://support.epostraders.co.uk/support-files/documents/3/l7O-TM-T88II_TechnicalRefGuide.pdf
"TM-T88II":
inherits: epson
columns:
a: 42
b: 56
codePages:
- cp437 # 0
- Katakana # 1
- cp850 # 2
- cp860 # 3
- cp863 # 4
- cp865 # 5
- cp858 # 19
- blank
# http://support.epostraders.co.uk/support-files/documents/3/l7O-TM-T88II_TechnicalRefGuide.pdf
"TM-T88III":
inherits: epson
columns:
a: 42
b: 56
codePages:
- CP437 # 0
- Katakana # 1
- CP850 # 2
- CP860 # 3
- CP863 # 4
- CP865 # 5
- PC1252 # 16
- CP866 # 17
- CP852 # 18
- CP858 # 19
- blank
"TM-P80":
inherits: epson
defaultColumnConfig: default
columnConfigs:
default: {'a': 48, 'b': 64, 'kanji': 24}
'42_emulation': {'a': 42, 'b': 60, 'kanji': 21}
"TM-P60II 2":
inherits: epson
columnConfigs:
'58mm_paper': {'a': 35, 'b': 42, 'c': 52}
'60mm_paper': {'a': 36, 'b': 43, 'c': 54}
"TM-P20 2":
inherits: epson
# Has 5 fonts!
"TM-T90":
inherits: epson
colors:
- black
- red

View File

@ -55,11 +55,18 @@ _CUT_PAPER = lambda m: GS + b'V' + m
PAPER_FULL_CUT = _CUT_PAPER(b'\x00') # Full cut paper PAPER_FULL_CUT = _CUT_PAPER(b'\x00') # Full cut paper
PAPER_PART_CUT = _CUT_PAPER(b'\x01') # Partial cut paper PAPER_PART_CUT = _CUT_PAPER(b'\x01') # Partial cut paper
# Beep
BEEP = b'\x07'
# Panel buttons (e.g. the FEED button) # Panel buttons (e.g. the FEED button)
_PANEL_BUTTON = lambda n: ESC + b'c5' + six.int2byte(n) _PANEL_BUTTON = lambda n: ESC + b'c5' + six.int2byte(n)
PANEL_BUTTON_ON = _PANEL_BUTTON(0) # enable all panel buttons PANEL_BUTTON_ON = _PANEL_BUTTON(0) # enable all panel buttons
PANEL_BUTTON_OFF = _PANEL_BUTTON(1) # disable all panel buttons PANEL_BUTTON_OFF = _PANEL_BUTTON(1) # disable all panel buttons
# Sheet modes
SHEET_SLIP_MODE = ESC + b'\x63\x30\x04' # slip paper
SHEET_ROLL_MODE = ESC + b'\x63\x30\x01' # paper roll
# Text format # Text format
# TODO: Acquire the "ESC/POS Application Programming Guide for Paper Roll # TODO: Acquire the "ESC/POS Application Programming Guide for Paper Roll
# Printers" and tidy up this stuff too. # Printers" and tidy up this stuff too.
@ -93,14 +100,29 @@ TXT_UNDERL_ON = ESC + b'\x2d\x01' # Underline font 1-dot ON
TXT_UNDERL2_ON = ESC + b'\x2d\x02' # Underline font 2-dot ON TXT_UNDERL2_ON = ESC + b'\x2d\x02' # Underline font 2-dot ON
TXT_BOLD_OFF = ESC + b'\x45\x00' # Bold font OFF TXT_BOLD_OFF = ESC + b'\x45\x00' # Bold font OFF
TXT_BOLD_ON = ESC + b'\x45\x01' # Bold font ON TXT_BOLD_ON = ESC + b'\x45\x01' # Bold font ON
TXT_FONT_A = ESC + b'\x4d\x00' # Font type A
TXT_FONT_B = ESC + b'\x4d\x01' # Font type B
TXT_ALIGN_LT = ESC + b'\x61\x00' # Left justification TXT_ALIGN_LT = ESC + b'\x61\x00' # Left justification
TXT_ALIGN_CT = ESC + b'\x61\x01' # Centering TXT_ALIGN_CT = ESC + b'\x61\x01' # Centering
TXT_ALIGN_RT = ESC + b'\x61\x02' # Right justification TXT_ALIGN_RT = ESC + b'\x61\x02' # Right justification
TXT_INVERT_ON = GS + b'\x42\x01' # Inverse Printing ON TXT_INVERT_ON = GS + b'\x42\x01' # Inverse Printing ON
TXT_INVERT_OFF = GS + b'\x42\x00' # Inverse Printing OFF TXT_INVERT_OFF = GS + b'\x42\x00' # Inverse Printing OFF
# Fonts
SET_FONT = lambda n: ESC + b'\x4d' + n
TXT_FONT_A = SET_FONT(b'\x00') # Font type A
TXT_FONT_B = SET_FONT(b'\x01') # Font type B
# Text colors
TXT_COLOR_BLACK = ESC + b'\x72\x00' # Default Color
TXT_COLOR_RED = ESC + b'\x72\x01' # Alternative Color (Usually Red)
# Spacing
LINESPACING_RESET = ESC + b'2'
LINESPACING_FUNCS = {
60: ESC + b'A', # line_spacing/60 of an inch, 0 <= line_spacing <= 85
360: ESC + b'+', # line_spacing/360 of an inch, 0 <= line_spacing <= 255
180: ESC + b'3', # line_spacing/180 of an inch, 0 <= line_spacing <= 255
}
CODEPAGE_CHANGE = ESC + b'\x74' CODEPAGE_CHANGE = ESC + b'\x74'
# Char code table # Char code table

View File

@ -24,7 +24,7 @@ from .magicencode import MagicEncode
from abc import ABCMeta, abstractmethod # abstract base class support from abc import ABCMeta, abstractmethod # abstract base class support
from escpos.image import EscposImage from escpos.image import EscposImage
from escpos.capabilities import get_profile from escpos.capabilities import get_profile, BARCODE_B
@six.add_metaclass(ABCMeta) @six.add_metaclass(ABCMeta)
@ -232,7 +232,8 @@ class Escpos(object):
else: else:
self.magic.force_encoding(code) self.magic.force_encoding(code)
def barcode(self, code, bc, height=64, width=3, pos="BELOW", font="A", align_ct=True, function_type="A"): def barcode(self, code, bc, height=64, width=3, pos="BELOW", font="A",
align_ct=True, function_type=None):
""" 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
@ -303,14 +304,40 @@ class Escpos(object):
issued. issued.
:type align_ct: bool :type align_ct: bool
:param function_type: Choose between ESCPOS function type A or B, depending on printer support and desired :param function_type: Choose between ESCPOS function type A or B,
barcode. depending on printer support and desired barcode. If not given,
the printer will attempt to automatically choose the correct
function based on the current profile.
*default*: A *default*: A
: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`
""" """
if function_type is None:
# Choose the function type automatically.
if bc in BARCODE_TYPES['A']:
function_type = 'A'
else:
if bc in BARCODE_TYPES['B']:
if not self.profile.supports(BARCODE_B):
raise BarcodeTypeError((
"Barcode type '{bc} not supported for "
"the current printer profile").format(bc=bc))
function_type = 'B'
else:
raise BarcodeTypeError((
"Barcode type '{bc} is not valid").format(bc=bc))
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 "
"{function_type}").format(
bc=bc,
function_type=function_type,
))
# Align Bar Code() # Align Bar Code()
if align_ct: if align_ct:
self._raw(TXT_ALIGN_CT) self._raw(TXT_ALIGN_CT)
@ -339,14 +366,6 @@ class Escpos(object):
else: # DEFAULT POSITION: BELOW else: # DEFAULT POSITION: BELOW
self._raw(BARCODE_TXT_BLW) self._raw(BARCODE_TXT_BLW)
bc_types = BARCODE_TYPES[function_type.upper()]
if bc.upper() not in bc_types.keys():
# TODO: Raise a better error, or fix the message of this error type
raise BarcodeTypeError("Barcode type {bc} not valid for barcode function type {function_type}".format(
bc=bc,
function_type=function_type,
))
self._raw(bc_types[bc.upper()]) self._raw(bc_types[bc.upper()])
if function_type.upper() == "B": if function_type.upper() == "B":
@ -385,8 +404,8 @@ class Escpos(object):
col_count = self.profile.get_columns(font) if columns is None else columns col_count = self.profile.get_columns(font) if columns is None else columns
self.text(textwrap.fill(txt, col_count)) self.text(textwrap.fill(txt, col_count))
def set(self, align='left', font='a', text_type='normal', width=1, height=1, density=9, invert=False, smooth=False, def set(self, align='left', font='a', text_type='normal', width=1,
flip=False): height=1, density=9, invert=False, smooth=False, flip=False):
""" Set text properties by sending them to the printer """ Set text properties by sending them to the printer
:param align: horizontal position for text, possible values are: :param align: horizontal position for text, possible values are:
@ -396,7 +415,9 @@ class Escpos(object):
* RIGHT * RIGHT
*default*: LEFT *default*: LEFT
:param font: font type, possible values are A or B, *default*: A
:param font: font given as an index, a name, or one of the
special values 'a' or 'b', refering to fonts 0 and 1.
:param text_type: text type, possible values are: :param text_type: text type, possible values are:
* B for bold * B for bold
@ -461,10 +482,8 @@ class Escpos(object):
self._raw(TXT_BOLD_OFF) self._raw(TXT_BOLD_OFF)
self._raw(TXT_UNDERL_OFF) self._raw(TXT_UNDERL_OFF)
# Font # Font
if font.upper() == "B": self._raw(SET_FONT(six.int2byte(self.profile.get_font(font))))
self._raw(TXT_FONT_B)
else: # DEFAULT FONT: A
self._raw(TXT_FONT_A)
# Align # Align
if align.upper() == "CENTER": if align.upper() == "CENTER":
self._raw(TXT_ALIGN_CT) self._raw(TXT_ALIGN_CT)
@ -499,6 +518,35 @@ class Escpos(object):
else: else:
self._raw(TXT_INVERT_OFF) self._raw(TXT_INVERT_OFF)
def line_spacing(self, spacing=None, divisor=180):
""" Set line character spacing.
If no spacing is given, we reset it to the default.
There are different commands for setting the line spacing, using
a different denominator:
'+'' line_spacing/360 of an inch, 0 <= line_spacing <= 255
'3' line_spacing/180 of an inch, 0 <= line_spacing <= 255
'A' line_spacing/60 of an inch, 0 <= line_spacing <= 85
Some printers may not support all of them. The most commonly
available command (using a divisor of 180) is chosen.
"""
if spacing is None:
self._raw(LINESPACING_RESET)
return
if divisor not in LINESPACING_FUNCS:
raise ValueError("divisor must be either 360, 180 or 60")
if (divisor in [360, 180] \
and (not(0 <= spacing <= 255))):
raise ValueError("spacing must be a int between 0 and 255 when divisor is 360 or 180")
if divisor == 60 and (not(0 <= spacing <= 85)):
raise ValueError("spacing must be a int between 0 and 85 when divisor is 60")
self._raw(LINESPACING_FUNCS[divisor] + six.int2byte(spacing))
def cut(self, mode=''): def cut(self, mode=''):
""" Cut paper. """ Cut paper.

View File

@ -0,0 +1,34 @@
#!/usr/bin/python
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
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
import pytest
@pytest.mark.parametrize("bctype,data,expected", [
('EAN13', '4006381333931',
b'\x1ba\x01\x1dh@\x1dw\x03\x1df\x00\x1dH\x02\x1dk\x024006381333931\x00')
])
def test_barcode(bctype, data, expected):
instance = printer.Dummy()
instance.barcode(data, bctype)
assert instance.output == expected
@pytest.mark.parametrize("bctype,supports_b", [
('invalid', True),
('CODE128', False),
])
def test_lacks_support(bctype, supports_b):
profile = Profile(features={BARCODE_B: supports_b})
instance = printer.Dummy(profile=profile)
with pytest.raises(BarcodeTypeError):
instance.barcode('test', bctype)
assert instance.output == b''

View File

@ -16,13 +16,12 @@ import mock
from hypothesis import given from hypothesis import given
import hypothesis.strategies as st import hypothesis.strategies as st
from escpos.printer import Dummy from escpos.printer import Dummy
import escpos.printer as printer
@given(text=st.text()) @given(text=st.text())
def test_function_text_dies_ist_ein_test_lf(text): def test_function_text_dies_ist_ein_test_lf(text):
"""test the text printing function with simple string and compare output""" """test the text printing function with simple string and compare output"""
instance = printer.Dummy() instance = Dummy()
instance.magic.encode_text = mock.Mock() instance.magic.encode_text = mock.Mock()
instance.text(text) instance.text(text)
instance.magic.encode_text.assert_called_with(txt=text) instance.magic.encode_text.assert_called_with(txt=text)
@ -33,4 +32,4 @@ def test_block_text():
printer.block_text( printer.block_text(
"All the presidents men were eating falafel for breakfast.", font='a') "All the presidents men were eating falafel for breakfast.", font='a')
assert printer.output == \ assert printer.output == \
'All the presidents men were eating falafel\nfor breakfast.' b'All the presidents men were eating falafel\nfor breakfast.'

26
test/test_functions.py Normal file
View File

@ -0,0 +1,26 @@
from nose.tools import assert_raises
from escpos.printer import Dummy
def test_line_spacing_code_gen():
printer = Dummy()
printer.line_spacing(10)
assert printer.output == b'\x1b3\n'
def test_line_spacing_rest():
printer = Dummy()
printer.line_spacing()
assert printer.output == b'\x1b2'
def test_line_spacing_error_handling():
printer = Dummy()
with assert_raises(ValueError):
printer.line_spacing(99, divisor=44)
with assert_raises(ValueError):
printer.line_spacing(divisor=80, spacing=86)
with assert_raises(ValueError):
printer.line_spacing(divisor=360, spacing=256)
with assert_raises(ValueError):
printer.line_spacing(divisor=180, spacing=256)

34
test/test_profile.py Normal file
View File

@ -0,0 +1,34 @@
import pytest
from escpos.capabilities import get_profile, NotSupported, BARCODE_B, Profile
@pytest.fixture
def profile():
return get_profile('default')
class TestBaseProfile:
def test_get_font(self, profile):
with pytest.raises(NotSupported):
assert profile.get_font('3')
assert profile.get_font(1) == 1
assert profile.get_font('a') == 0
def test_supports(self, profile):
assert not profile.supports('asdf asdf')
assert profile.supports(BARCODE_B)
def test_get_columns(self, profile):
assert profile.get_columns('a') > 5
with pytest.raises(NotSupported):
assert profile.get_columns('asdfasdf')
class TestCustomProfile:
def test_columns(self):
assert Profile(columns=10).get_columns('sdfasdf') == 10
def test_features(self):
assert Profile(features={'foo': True}).supports('foo')

View File

@ -6,8 +6,10 @@ deps = nose
coverage coverage
scripttest scripttest
mock mock
pytest
pytest-cov
hypothesis hypothesis
commands = nosetests --with-coverage --cover-erase --cover-branches commands = py.test --cov reports
[testenv:docs] [testenv:docs]
basepython = python basepython = python