refactor set method (#547)

* add type annotations
* Update setup.cfg
* improve mypy and test config
* get_profile_class returns a baseProfile
* mute mypy type issue
* add set_with_default
* docstring
* add test for empty call
* correct type annotations in set_with_default
* improve tests
* test for exception
* sort with isort
* add default value to get call
* empty string has the same effect: will not be found --> None
* add mypy test to workflow
* explicitely call mypy
* update spelling
This commit is contained in:
Patrick Kanzler 2023-09-19 07:51:39 +02:00 committed by GitHub
parent adcde8abda
commit 6d0c475b9a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 236 additions and 86 deletions

View File

@ -48,6 +48,11 @@ jobs:
tox tox
env: env:
ESCPOS_CAPABILITIES_FILE: /home/runner/work/python-escpos/python-escpos/capabilities-data/dist/capabilities.json ESCPOS_CAPABILITIES_FILE: /home/runner/work/python-escpos/python-escpos/capabilities-data/dist/capabilities.json
- name: Test mypy with tox
run: |
tox -e mypy
env:
ESCPOS_CAPABILITIES_FILE: /home/runner/work/python-escpos/python-escpos/capabilities-data/dist/capabilities.json
- name: Upload coverage to Codecov - name: Upload coverage to Codecov
uses: codecov/codecov-action@v3 uses: codecov/codecov-action@v3
with: with:

View File

@ -8,7 +8,7 @@ import time
from contextlib import ExitStack from contextlib import ExitStack
from os import environ, path from os import environ, path
from tempfile import mkdtemp from tempfile import mkdtemp
from typing import Any, Dict, Optional from typing import Any, Dict, Optional, Type
import importlib_resources import importlib_resources
import six import six
@ -148,7 +148,7 @@ def get_profile(name: Optional[str] = None, **kwargs):
CLASS_CACHE = {} CLASS_CACHE = {}
def get_profile_class(name: str): def get_profile_class(name: str) -> Type[BaseProfile]:
"""Load a profile class. """Load a profile class.
For the given profile name, load the data from the external For the given profile name, load the data from the external
@ -174,7 +174,11 @@ def clean(s):
return str(s) return str(s)
class Profile(get_profile_class("default")): # mute the mypy type issue with this dynamic base class function for now (: Any)
ProfileBaseClass: Any = get_profile_class("default")
class Profile(ProfileBaseClass):
"""Profile class for user usage. """Profile class for user usage.
For users, who want to provide their own profile. For users, who want to provide their own profile.

View File

@ -574,14 +574,14 @@ class Escpos(object):
def _hw_barcode( def _hw_barcode(
self, self,
code, code: str,
bc, bc: str,
height: int = 64, height: int = 64,
width: int = 3, width: int = 3,
pos: str = "BELOW", pos: str = "BELOW",
font: str = "A", font: str = "A",
align_ct: bool = True, align_ct: bool = True,
function_type=None, function_type: Optional[str] = None,
check: bool = True, check: bool = True,
) -> None: ) -> None:
"""Print Barcode. """Print Barcode.
@ -662,8 +662,8 @@ class Escpos(object):
:py:exc:`~escpos.exceptions.BarcodeCodeError` :py:exc:`~escpos.exceptions.BarcodeCodeError`
""" """
# If function_type is specified, otherwise use guessing. # If function_type is specified, otherwise use guessing.
ft_guess = [ft for ft in ["A", "B"] if bc in BARCODE_TYPES.get(ft)] ft_guess = [ft for ft in ["A", "B"] if bc in BARCODE_TYPES.get(ft, {"": b""})]
ft_guess = ft_guess or [None] ft_guess = ft_guess or [""]
function_type = function_type or ft_guess[0] function_type = function_type or ft_guess[0]
if not function_type or not BARCODE_TYPES.get(function_type.upper()): if not function_type or not BARCODE_TYPES.get(function_type.upper()):
@ -864,22 +864,117 @@ class Escpos(object):
def set( def set(
self, self,
align="left", align: Optional[str] = None,
font="a", font: Optional[str] = None,
bold=False, bold: Optional[bool] = None,
underline=0, underline: Optional[int] = None,
width=1, width: Optional[int] = None,
height=1, height: Optional[int] = None,
density=9, density: Optional[int] = None,
invert=False, invert: Optional[bool] = None,
smooth=False, smooth: Optional[bool] = None,
flip=False, flip: Optional[bool] = None,
double_width=False, normal_textsize: Optional[bool] = None,
double_height=False, double_width: Optional[bool] = None,
custom_size=False, double_height: Optional[bool] = None,
): custom_size: Optional[bool] = None,
) -> None:
"""Set text properties by sending them to the printer. """Set text properties by sending them to the printer.
If a value for a parameter is not supplied, nothing is sent
for this type of format.
:param align: horizontal position for text, possible values are:
* 'center'
* 'left'
* 'right'
:param font: font given as an index, a name, or one of the
special values 'a' or 'b', referring to fonts 0 and 1.
:param bold: text in bold
:param underline: underline mode for text, decimal range 0-2
:param normal_textsize: switch to normal text size if True
:param double_height: doubles the height of the text
:param double_width: doubles the width of the text
:param custom_size: uses custom size specified by width and height
parameters. Cannot be used with double_width or double_height.
:param width: text width multiplier when custom_size is used, decimal range 1-8
:param height: text height multiplier when custom_size is used, decimal range 1-8
:param density: print density, value from 0-8, if something else is supplied the density remains unchanged
:param invert: True enables white on black printing
:param smooth: True enables text smoothing. Effective on 4x4 size text and larger
:param flip: True enables upside-down printing
"""
if custom_size:
if (
isinstance(width, int)
and isinstance(height, int)
and 1 <= width <= 8
and 1 <= height <= 8
):
size_byte = TXT_STYLE["width"][width] + TXT_STYLE["height"][height]
self._raw(TXT_SIZE + six.int2byte(size_byte))
else:
raise SetVariableError()
elif normal_textsize or double_height or double_width:
self._raw(TXT_NORMAL)
if double_width and double_height:
self._raw(TXT_STYLE["size"]["2x"])
elif double_width:
self._raw(TXT_STYLE["size"]["2w"])
elif double_height:
self._raw(TXT_STYLE["size"]["2h"])
else:
self._raw(TXT_STYLE["size"]["normal"])
else:
# no text size handling requested
pass
if flip is not None:
self._raw(TXT_STYLE["flip"][flip])
if smooth is not None:
self._raw(TXT_STYLE["smooth"][smooth])
if bold is not None:
self._raw(TXT_STYLE["bold"][bold])
if underline is not None:
self._raw(TXT_STYLE["underline"][underline])
if font is not None:
self._raw(SET_FONT(six.int2byte(self.profile.get_font(font))))
if align is not None:
self._raw(TXT_STYLE["align"][align])
if density is not None and density != 9:
self._raw(TXT_STYLE["density"][density])
if invert is not None:
self._raw(TXT_STYLE["invert"][invert])
def set_with_default(
self,
align: Optional[str] = "left",
font: Optional[str] = "a",
bold: Optional[bool] = False,
underline: Optional[int] = 0,
width: Optional[int] = 1,
height: Optional[int] = 1,
density: Optional[int] = 9,
invert: Optional[bool] = False,
smooth: Optional[bool] = False,
flip: Optional[bool] = False,
double_width: Optional[bool] = False,
double_height: Optional[bool] = False,
custom_size: Optional[bool] = False,
) -> None:
"""Set default text properties by sending them to the printer.
This function has the behavior of the `set()`-method from before
version 3.
If a parameter to this method is not supplied, a default value
will be sent.
Otherwise this method forwards the values to the
:py:meth:`escpos.Escpos.set()`.
:param align: horizontal position for text, possible values are: :param align: horizontal position for text, possible values are:
* 'center' * 'center'
@ -902,54 +997,24 @@ class Escpos(object):
:param invert: True enables white on black printing, *default*: False :param invert: True enables white on black printing, *default*: False
:param smooth: True enables text smoothing. Effective on 4x4 size text and larger, *default*: False :param smooth: True enables text smoothing. Effective on 4x4 size text and larger, *default*: False
:param flip: True enables upside-down printing, *default*: False :param flip: True enables upside-down printing, *default*: False
:type font: str
:type invert: bool
:type bold: bool
:type underline: bool
:type smooth: bool
:type flip: bool
:type custom_size: bool
:type double_width: bool
:type double_height: bool
:type align: str
:type width: int
:type height: int
:type density: int
""" """
if custom_size: normal_textsize = not custom_size and not double_width and not double_height
if ( self.set(
isinstance(width, int) align=align,
and isinstance(height, int) font=font,
and 1 <= width <= 8 bold=bold,
and 1 <= height <= 8 underline=underline,
): width=width,
size_byte = TXT_STYLE["width"][width] + TXT_STYLE["height"][height] height=height,
self._raw(TXT_SIZE + six.int2byte(size_byte)) density=density,
else: invert=invert,
raise SetVariableError() smooth=smooth,
else: flip=flip,
self._raw(TXT_NORMAL) normal_textsize=normal_textsize,
if double_width and double_height: double_width=double_width,
self._raw(TXT_STYLE["size"]["2x"]) double_height=double_height,
elif double_width: custom_size=custom_size,
self._raw(TXT_STYLE["size"]["2w"]) )
elif double_height:
self._raw(TXT_STYLE["size"]["2h"])
else:
self._raw(TXT_STYLE["size"]["normal"])
self._raw(TXT_STYLE["flip"][flip])
self._raw(TXT_STYLE["smooth"][smooth])
self._raw(TXT_STYLE["bold"][bold])
self._raw(TXT_STYLE["underline"][underline])
self._raw(SET_FONT(six.int2byte(self.profile.get_font(font))))
self._raw(TXT_STYLE["align"][align])
if density != 9:
self._raw(TXT_STYLE["density"][density])
self._raw(TXT_STYLE["invert"][invert])
def line_spacing(self, spacing: Optional[int] = None, divisor: int = 180) -> None: def line_spacing(self, spacing: Optional[int] = None, divisor: int = 180) -> None:
"""Set line character spacing. """Set line character spacing.

View File

@ -1,14 +1,15 @@
import pytest
import six import six
import escpos.printer as printer import escpos.printer as printer
from escpos.constants import SET_FONT, TXT_NORMAL, TXT_SIZE, TXT_STYLE from escpos.constants import SET_FONT, TXT_NORMAL, TXT_SIZE, TXT_STYLE
from escpos.exceptions import SetVariableError
# Default test, please copy and paste this block to test set method calls
def test_default_values(): def test_default_values_with_default():
"""Default test, please copy and paste this block to test set method calls"""
instance = printer.Dummy() instance = printer.Dummy()
instance.set() instance.set_with_default()
expected_sequence = ( expected_sequence = (
TXT_NORMAL, TXT_NORMAL,
@ -25,12 +26,20 @@ def test_default_values():
assert instance.output == b"".join(expected_sequence) assert instance.output == b"".join(expected_sequence)
def test_default_values():
"""Default test"""
instance = printer.Dummy()
instance.set()
assert instance.output == b""
# Size tests # Size tests
def test_set_size_2h(): def test_set_size_2h():
instance = printer.Dummy() instance = printer.Dummy()
instance.set(double_height=True) instance.set_with_default(double_height=True)
expected_sequence = ( expected_sequence = (
TXT_NORMAL, TXT_NORMAL,
@ -47,9 +56,21 @@ def test_set_size_2h():
assert instance.output == b"".join(expected_sequence) assert instance.output == b"".join(expected_sequence)
def test_set_size_2h_no_default():
instance = printer.Dummy()
instance.set(double_height=True)
expected_sequence = (
TXT_NORMAL,
TXT_STYLE["size"]["2h"], # Double height text size
)
assert instance.output == b"".join(expected_sequence)
def test_set_size_2w(): def test_set_size_2w():
instance = printer.Dummy() instance = printer.Dummy()
instance.set(double_width=True) instance.set_with_default(double_width=True)
expected_sequence = ( expected_sequence = (
TXT_NORMAL, TXT_NORMAL,
@ -66,9 +87,21 @@ def test_set_size_2w():
assert instance.output == b"".join(expected_sequence) assert instance.output == b"".join(expected_sequence)
def test_set_size_2w_no_default():
instance = printer.Dummy()
instance.set(double_width=True)
expected_sequence = (
TXT_NORMAL,
TXT_STYLE["size"]["2w"], # Double width text size
)
assert instance.output == b"".join(expected_sequence)
def test_set_size_2x(): def test_set_size_2x():
instance = printer.Dummy() instance = printer.Dummy()
instance.set(double_height=True, double_width=True) instance.set_with_default(double_height=True, double_width=True)
expected_sequence = ( expected_sequence = (
TXT_NORMAL, TXT_NORMAL,
@ -85,9 +118,21 @@ def test_set_size_2x():
assert instance.output == b"".join(expected_sequence) assert instance.output == b"".join(expected_sequence)
def test_set_size_2x_no_default():
instance = printer.Dummy()
instance.set(double_width=True, double_height=True)
expected_sequence = (
TXT_NORMAL,
TXT_STYLE["size"]["2x"], # Quad area text size
)
assert instance.output == b"".join(expected_sequence)
def test_set_size_custom(): def test_set_size_custom():
instance = printer.Dummy() instance = printer.Dummy()
instance.set(custom_size=True, width=8, height=7) instance.set_with_default(custom_size=True, width=8, height=7)
expected_sequence = ( expected_sequence = (
TXT_SIZE, # Custom text size, no normal reset TXT_SIZE, # Custom text size, no normal reset
@ -104,12 +149,34 @@ def test_set_size_custom():
assert instance.output == b"".join(expected_sequence) assert instance.output == b"".join(expected_sequence)
@pytest.mark.parametrize("width", [1, 8])
@pytest.mark.parametrize("height", [1, 8])
def test_set_size_custom_no_default(width, height):
instance = printer.Dummy()
instance.set(custom_size=True, width=width, height=height)
expected_sequence = (
TXT_SIZE, # Custom text size, no normal reset
six.int2byte(TXT_STYLE["width"][width] + TXT_STYLE["height"][height]),
)
assert instance.output == b"".join(expected_sequence)
@pytest.mark.parametrize("width", [None, 0, 9, 10, 4444])
@pytest.mark.parametrize("height", [None, 0, 9, 10, 4444])
def test_set_size_custom_invalid_input(width, height):
instance = printer.Dummy()
with pytest.raises(SetVariableError):
instance.set(custom_size=True, width=width, height=height)
# Flip # Flip
def test_set_flip(): def test_set_flip():
instance = printer.Dummy() instance = printer.Dummy()
instance.set(flip=True) instance.set_with_default(flip=True)
expected_sequence = ( expected_sequence = (
TXT_NORMAL, TXT_NORMAL,
@ -126,12 +193,21 @@ def test_set_flip():
assert instance.output == b"".join(expected_sequence) assert instance.output == b"".join(expected_sequence)
def test_set_flip_no_default():
instance = printer.Dummy()
instance.set(flip=True)
expected_sequence = (TXT_STYLE["flip"][True],) # Flip ON
assert instance.output == b"".join(expected_sequence)
# Smooth # Smooth
def test_smooth(): def test_smooth():
instance = printer.Dummy() instance = printer.Dummy()
instance.set(smooth=True) instance.set_with_default(smooth=True)
expected_sequence = ( expected_sequence = (
TXT_NORMAL, TXT_NORMAL,
@ -153,7 +229,7 @@ def test_smooth():
def test_set_bold(): def test_set_bold():
instance = printer.Dummy() instance = printer.Dummy()
instance.set(bold=True) instance.set_with_default(bold=True)
expected_sequence = ( expected_sequence = (
TXT_NORMAL, TXT_NORMAL,
@ -172,7 +248,7 @@ def test_set_bold():
def test_set_underline(): def test_set_underline():
instance = printer.Dummy() instance = printer.Dummy()
instance.set(underline=1) instance.set_with_default(underline=1)
expected_sequence = ( expected_sequence = (
TXT_NORMAL, TXT_NORMAL,
@ -191,7 +267,7 @@ def test_set_underline():
def test_set_underline2(): def test_set_underline2():
instance = printer.Dummy() instance = printer.Dummy()
instance.set(underline=2) instance.set_with_default(underline=2)
expected_sequence = ( expected_sequence = (
TXT_NORMAL, TXT_NORMAL,
@ -213,7 +289,7 @@ def test_set_underline2():
def test_align_center(): def test_align_center():
instance = printer.Dummy() instance = printer.Dummy()
instance.set(align="center") instance.set_with_default(align="center")
expected_sequence = ( expected_sequence = (
TXT_NORMAL, TXT_NORMAL,
@ -232,7 +308,7 @@ def test_align_center():
def test_align_right(): def test_align_right():
instance = printer.Dummy() instance = printer.Dummy()
instance.set(align="right") instance.set_with_default(align="right")
expected_sequence = ( expected_sequence = (
TXT_NORMAL, TXT_NORMAL,
@ -255,7 +331,7 @@ def test_align_right():
def test_densities(): def test_densities():
for density in range(8): for density in range(8):
instance = printer.Dummy() instance = printer.Dummy()
instance.set(density=density) instance.set_with_default(density=density)
expected_sequence = ( expected_sequence = (
TXT_NORMAL, TXT_NORMAL,
@ -278,7 +354,7 @@ def test_densities():
def test_invert(): def test_invert():
instance = printer.Dummy() instance = printer.Dummy()
instance.set(invert=True) instance.set_with_default(invert=True)
expected_sequence = ( expected_sequence = (
TXT_NORMAL, TXT_NORMAL,