start removal of six and improve type annotation (#607)

* fix unfinished Python2 -> 3 translation
* remove some six usage
* annotate
* fix regression in Six removal
* mypy: self.enf is always defined
* fix return type of cups.py
* Usb idVendor/idProduct are integers
* self.default_args is always defined
* tweak usb_args, PEP589 is better
* lp.py: reassure mypy
* correctly cast call to CashDrawerError()
* update CUPS test
* add missing close() method in metaclass
* document a bug in typeshed
* query_status() returns bytes as seen in constants.py
* remove more SIX usage
* test examples too
* remove type comment where type is annotated
* adapt behavior of cups printer to match other implementations

---------

Co-authored-by: Patrick Kanzler <dev@pkanzler.de>
Co-authored-by: Patrick Kanzler <4189642+patkan@users.noreply.github.com>
This commit is contained in:
Alexandre Detiste 2023-12-16 22:02:24 +01:00 committed by GitHub
parent 06bdb56937
commit 66a2e78e16
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 245 additions and 237 deletions

View File

@ -3,8 +3,6 @@
import sys import sys
import six
from escpos import printer from escpos import printer
from escpos.constants import ( from escpos.constants import (
CODEPAGE_CHANGE, CODEPAGE_CHANGE,
@ -38,7 +36,7 @@ def print_codepage(printer, codepage):
"""Print a codepage.""" """Print a codepage."""
if codepage.isdigit(): if codepage.isdigit():
codepage = int(codepage) codepage = int(codepage)
printer._raw(CODEPAGE_CHANGE + six.int2byte(codepage)) printer._raw(CODEPAGE_CHANGE + bytes((codepage,)))
printer._raw("after") printer._raw("after")
else: else:
printer.charcode(codepage) printer.charcode(codepage)
@ -58,7 +56,9 @@ def print_codepage(printer, codepage):
printer.set() printer.set()
for y in range(0, 16): for y in range(0, 16):
byte = six.int2byte(x * 16 + y) byte = bytes(
(x * 16 + y),
)
if byte in (ESC, CTL_LF, CTL_FF, CTL_CR, CTL_HT, CTL_VT): if byte in (ESC, CTL_LF, CTL_FF, CTL_CR, CTL_HT, CTL_VT):
byte = " " byte = " "

View File

@ -18,8 +18,8 @@ import calendar
import json import json
import os import os
import time import time
import urllib
from datetime import datetime from datetime import datetime
from urllib.request import urlopen
from escpos.printer import Usb from escpos.printer import Usb
@ -93,7 +93,7 @@ url = (
+ LONG + LONG
+ "?exclude=[alerts,minutely,hourly,flags]&units=si" + "?exclude=[alerts,minutely,hourly,flags]&units=si"
) # change last bit to 'us' for Fahrenheit ) # change last bit to 'us' for Fahrenheit
response = urllib.urlopen(url) response = urlopen(url)
data = json.loads(response.read()) data = json.loads(response.read())
printer.print_and_feed(n=1) printer.print_and_feed(n=1)

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""python-escpos enables you to manipulate escpos-printers.""" """python-escpos enables you to manipulate escpos-printers."""
__all__ = ["constants", "escpos", "exceptions", "printer"] __all__ = ["constants", "escpos", "exceptions", "printer", "__version__"]
try: try:
from .version import version as __version__ # noqa from .version import version as __version__ # noqa

View File

@ -11,7 +11,6 @@ from tempfile import mkdtemp
from typing import Any, Dict, Optional, Type from typing import Any, Dict, Optional, Type
import importlib_resources import importlib_resources
import six
import yaml import yaml
logging.basicConfig() logging.basicConfig()
@ -92,7 +91,7 @@ class NotSupported(Exception):
BARCODE_B = "barcodeB" BARCODE_B = "barcodeB"
class BaseProfile(object): class BaseProfile:
"""This represents a printer profile. """This represents a printer profile.
A printer profile knows about the number of columns, supported A printer profile knows about the number of columns, supported
@ -111,20 +110,22 @@ class BaseProfile(object):
Makes sure that the requested `font` is valid. Makes sure that the requested `font` is valid.
""" """
font = {"a": 0, "b": 1}.get(font, font) font = {"a": 0, "b": 1}.get(font, font)
if not six.text_type(font) in self.fonts: if not str(font) in self.fonts:
raise NotSupported(f'"{font}" is not a valid font in the current profile') raise NotSupported(f'"{font}" is not a valid font in the current profile')
return font return font
def get_columns(self, font): def get_columns(self, font) -> int:
"""Return the number of columns for the given font.""" """Return the number of columns for the given font."""
font = self.get_font(font) font = self.get_font(font)
return self.fonts[six.text_type(font)]["columns"] columns = self.fonts[str(font)]["columns"]
assert type(columns) is int
return columns
def supports(self, feature): def supports(self, feature) -> bool:
"""Return true/false for the given feature.""" """Return true/false for the given feature."""
return self.features.get(feature) return self.features.get(feature)
def get_code_pages(self): def get_code_pages(self) -> Dict[str, int]:
"""Return the support code pages as a ``{name: index}`` dict.""" """Return the support code pages as a ``{name: index}`` dict."""
return {v: k for k, v in self.codePages.items()} return {v: k for k, v in self.codePages.items()}
@ -161,7 +162,7 @@ def get_profile_class(name: str) -> Type[BaseProfile]:
return CLASS_CACHE[name] return CLASS_CACHE[name]
def clean(s): def clean(s: str) -> str:
"""Clean profile name.""" """Clean profile name."""
# Remove invalid characters # Remove invalid characters
s = re.sub("[^0-9a-zA-Z_]", "", s) s = re.sub("[^0-9a-zA-Z_]", "", s)
@ -180,14 +181,14 @@ class Profile(ProfileBaseClass):
For users, who want to provide their own profile. For users, who want to provide their own profile.
""" """
def __init__(self, columns=None, features=None): def __init__(self, columns: Optional[int] = None, features=None) -> None:
"""Initialize profile.""" """Initialize profile."""
super(Profile, self).__init__() super(Profile, self).__init__()
self.columns = columns self.columns = columns
self.features = features or {} self.features = features or {}
def get_columns(self, font): def get_columns(self, font) -> int:
"""Get column count of printer.""" """Get column count of printer."""
if self.columns is not None: if self.columns is not None:
return self.columns return self.columns

View File

@ -20,15 +20,13 @@ except ImportError:
pass # noqa pass # noqa
import sys import sys
import six
from . import config from . import config
from . import printer as escpos_printer_module from . import printer as escpos_printer_module
from . import version from . import version
# Must be defined before it's used in DEMO_FUNCTIONS # Must be defined before it's used in DEMO_FUNCTIONS
def str_to_bool(string): def str_to_bool(string: str) -> bool:
"""Convert string to bool. """Convert string to bool.
Used as a type in argparse so that we get back a proper Used as a type in argparse so that we get back a proper
@ -563,7 +561,7 @@ def generate_parser() -> argparse.ArgumentParser:
return parser return parser
def main(): def main() -> None:
"""Handle main entry point of CLI script. """Handle main entry point of CLI script.
Handles loading of configuration and creating and processing of command Handles loading of configuration and creating and processing of command
@ -580,9 +578,7 @@ def main():
if not args_dict: if not args_dict:
parser.print_help() parser.print_help()
sys.exit() sys.exit()
command_arguments = dict( command_arguments = dict([k, v] for k, v in args_dict.items() if v is not None)
[k, v] for k, v in six.iteritems(args_dict) if v is not None
)
# If version should be printed, do this, then exit # If version should be printed, do this, then exit
print_version = command_arguments.pop("version", None) print_version = command_arguments.pop("version", None)
@ -621,7 +617,7 @@ def main():
globals()[target_command](**command_arguments) globals()[target_command](**command_arguments)
def demo(printer, **kwargs): def demo(printer, **kwargs) -> None:
"""Print demos. """Print demos.
Called when CLI is passed `demo`. This function Called when CLI is passed `demo`. This function

View File

@ -11,7 +11,7 @@ import yaml
from . import exceptions, printer from . import exceptions, printer
class Config(object): class Config:
"""Configuration handler class. """Configuration handler class.
This class loads configuration from a default or specified directory. It This class loads configuration from a default or specified directory. It
@ -21,7 +21,7 @@ class Config(object):
_app_name = "python-escpos" _app_name = "python-escpos"
_config_file = "config.yaml" _config_file = "config.yaml"
def __init__(self): def __init__(self) -> None:
"""Initialize configuration. """Initialize configuration.
Remember to add anything that needs to be reset between configurations Remember to add anything that needs to be reset between configurations
@ -33,7 +33,7 @@ class Config(object):
self._printer_name = None self._printer_name = None
self._printer_config = None self._printer_config = None
def _reset_config(self): def _reset_config(self) -> None:
"""Clear the loaded configuration. """Clear the loaded configuration.
If we are loading a changed config, we don't want to have leftover If we are loading a changed config, we don't want to have leftover

View File

@ -46,9 +46,7 @@ HW_RESET: bytes = ESC + b"\x3f\x0a\x00" # Reset printer hardware
# (TODO: Where is this specified?) # (TODO: Where is this specified?)
# Cash Drawer (ESC p <pin> <on time: 2*ms> <off time: 2*ms>) # Cash Drawer (ESC p <pin> <on time: 2*ms> <off time: 2*ms>)
_CASH_DRAWER = ( _CASH_DRAWER = lambda m, t1="", t2="": ESC + b"p" + m + bytes((t1, t2))
lambda m, t1="", t2="": ESC + b"p" + m + six.int2byte(t1) + six.int2byte(t2)
)
#: decimal cash drawer kick sequence #: decimal cash drawer kick sequence
CD_KICK_DEC_SEQUENCE = ( CD_KICK_DEC_SEQUENCE = (

View File

@ -9,13 +9,14 @@ This module contains the abstract base class :py:class:`Escpos`.
:copyright: Copyright (c) 2012-2017 Bashlinux and python-escpos :copyright: Copyright (c) 2012-2017 Bashlinux and python-escpos
:license: MIT :license: MIT
""" """
from __future__ import annotations
import textwrap import textwrap
import warnings import warnings
from abc import ABCMeta, abstractmethod # abstract base class support from abc import ABCMeta, abstractmethod # abstract base class support
from re import match as re_match from re import match as re_match
from typing import List, Literal, Optional, Union from types import TracebackType
from typing import Any, Literal, Optional, Union
import barcode import barcode
import qrcode import qrcode
@ -107,8 +108,7 @@ SW_BARCODE_NAMES = {
} }
@six.add_metaclass(ABCMeta) class Escpos(object, metaclass=ABCMeta):
class Escpos(object):
"""ESC/POS Printer object. """ESC/POS Printer object.
This class is the abstract base class for an Esc/Pos-printer. The printer implementations are children of this This class is the abstract base class for an Esc/Pos-printer. The printer implementations are children of this
@ -154,6 +154,10 @@ class Escpos(object):
"""Open a printer device/connection.""" """Open a printer device/connection."""
pass pass
def close(self):
"""Close a printer device/connection."""
pass
@abstractmethod @abstractmethod
def _raw(self, msg: bytes) -> None: def _raw(self, msg: bytes) -> None:
"""Send raw data to the printer. """Send raw data to the printer.
@ -164,7 +168,7 @@ class Escpos(object):
""" """
pass pass
def _read(self): def _read(self) -> bytes:
"""Read from printer. """Read from printer.
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.
@ -250,7 +254,7 @@ class Escpos(object):
header = ( header = (
GS GS
+ b"v0" + b"v0"
+ six.int2byte(density_byte) + bytes((density_byte,))
+ self._int_low_high(im.width_bytes, 2) + self._int_low_high(im.width_bytes, 2)
+ self._int_low_high(im.height, 2) + self._int_low_high(im.height, 2)
) )
@ -263,8 +267,8 @@ class Escpos(object):
) )
tone = b"0" tone = b"0"
colors = b"1" colors = b"1"
ym = six.int2byte(1 if high_density_vertical else 2) ym = b"\x01" if high_density_vertical else b"\x02"
xm = six.int2byte(1 if high_density_horizontal else 2) xm = b"\x01" if high_density_horizontal else b"\x02"
header = tone + xm + ym + colors + img_header header = tone + xm + ym + colors + img_header
raster_data = im.to_raster_format() raster_data = im.to_raster_format()
self._image_send_graphics_data(b"0", b"p", header + raster_data) self._image_send_graphics_data(b"0", b"p", header + raster_data)
@ -847,7 +851,7 @@ class Escpos(object):
image = my_code.writer._image image = my_code.writer._image
self.image(image, impl=impl, center=center) self.image(image, impl=impl, center=center)
def text(self, txt): def text(self, txt: str) -> None:
"""Print alpha-numeric text. """Print alpha-numeric text.
The text has to be encoded in the currently selected codepage. The text has to be encoded in the currently selected codepage.
@ -856,10 +860,9 @@ class Escpos(object):
:param txt: text to be printed :param txt: text to be printed
:raises: :py:exc:`~escpos.exceptions.TextError` :raises: :py:exc:`~escpos.exceptions.TextError`
""" """
txt = six.text_type(txt) self.magic.write(str(txt))
self.magic.write(txt)
def textln(self, txt=""): def textln(self, txt: str = "") -> None:
"""Print alpha-numeric text with a newline. """Print alpha-numeric text with a newline.
The text has to be encoded in the currently selected codepage. The text has to be encoded in the currently selected codepage.
@ -870,7 +873,7 @@ class Escpos(object):
""" """
self.text(f"{txt}\n") self.text(f"{txt}\n")
def ln(self, count=1): def ln(self, count: int = 1) -> None:
"""Print a newline or more. """Print a newline or more.
:param count: number of newlines to print :param count: number of newlines to print
@ -881,7 +884,7 @@ class Escpos(object):
if count > 0: if count > 0:
self.text("\n" * count) self.text("\n" * count)
def block_text(self, txt, font="0", columns=None): def block_text(self, txt, font="0", columns=None) -> None:
"""Print text wrapped to specific columns. """Print text wrapped to specific columns.
Text has to be encoded in unicode. Text has to be encoded in unicode.
@ -1132,7 +1135,7 @@ class Escpos(object):
try: try:
self._raw(CD_KICK_DEC_SEQUENCE(*pin)) self._raw(CD_KICK_DEC_SEQUENCE(*pin))
except TypeError as err: except TypeError as err:
raise CashDrawerError(err) raise CashDrawerError(str(err))
def linedisplay_select(self, select_display: bool = False) -> None: def linedisplay_select(self, select_display: bool = False) -> None:
"""Select the line display or the printer. """Select the line display or the printer.
@ -1265,10 +1268,10 @@ class Escpos(object):
else: else:
self._raw(PANEL_BUTTON_OFF) self._raw(PANEL_BUTTON_OFF)
def query_status(self, mode: bytes) -> List[int]: def query_status(self, mode: bytes) -> bytes:
"""Query the printer for its status. """Query the printer for its status.
Returns an array of integers containing it. Returns byte array containing it.
:param mode: Integer that sets the status mode queried to the printer. :param mode: Integer that sets the status mode queried to the printer.
- RT_STATUS_ONLINE: Printer status. - RT_STATUS_ONLINE: Printer status.
@ -1288,7 +1291,7 @@ class Escpos(object):
return False return False
return not (status[0] & RT_MASK_ONLINE) return not (status[0] & RT_MASK_ONLINE)
def paper_status(self): def paper_status(self) -> int: # could be IntEnum
"""Query the paper status of the printer. """Query the paper status of the printer.
Returns 2 if there is plenty of paper, 1 if the paper has arrived to Returns 2 if there is plenty of paper, 1 if the paper has arrived to
@ -1305,6 +1308,8 @@ class Escpos(object):
return 1 return 1
if status[0] & RT_MASK_PAPER == RT_MASK_PAPER: if status[0] & RT_MASK_PAPER == RT_MASK_PAPER:
return 2 return 2
# not reached
return 0
def target(self, type: str = "ROLL") -> None: def target(self, type: str = "ROLL") -> None:
"""Select where to print to. """Select where to print to.
@ -1359,7 +1364,7 @@ class Escpos(object):
self._raw(BUZZER + six.int2byte(times) + six.int2byte(duration)) self._raw(BUZZER + six.int2byte(times) + six.int2byte(duration))
class EscposIO(object): class EscposIO:
r"""ESC/POS Printer IO object. r"""ESC/POS Printer IO object.
Allows the class to be used together with the `with`-statement. You have to define a printer instance Allows the class to be used together with the `with`-statement. You have to define a printer instance
@ -1405,12 +1410,12 @@ class EscposIO(object):
""" """
self.params.update(kwargs) self.params.update(kwargs)
def writelines(self, text, **kwargs): def writelines(self, text: str, **kwargs) -> None:
"""Print text.""" """Print text."""
params = dict(self.params) params = dict(self.params)
params.update(kwargs) params.update(kwargs)
if isinstance(text, six.text_type): if isinstance(text, str):
lines = text.split("\n") lines = text.split("\n")
elif isinstance(text, list) or isinstance(text, tuple): elif isinstance(text, list) or isinstance(text, tuple):
lines = text lines = text
@ -1423,23 +1428,22 @@ class EscposIO(object):
# TODO flush? or on print? (this should prob rather be handled by the _raw-method) # TODO flush? or on print? (this should prob rather be handled by the _raw-method)
for line in lines: for line in lines:
self.printer.set(**params) self.printer.set(**params)
if isinstance(text, six.text_type):
self.printer.text(f"{line}\n")
else:
self.printer.text(f"{line}\n") self.printer.text(f"{line}\n")
def close(self): def close(self) -> None:
"""Close printer. """Close printer.
Called upon closing the `with`-statement. Called upon closing the `with`-statement.
""" """
self.printer.close() self.printer.close()
def __enter__(self, **kwargs): def __enter__(self, **kwargs: Any) -> "EscposIO":
"""Enter context.""" """Enter context."""
return self return self
def __exit__(self, type, value, traceback): def __exit__(
self, type: type[BaseException], value: BaseException, traceback: TracebackType
) -> None:
"""Cut and close if configured. """Cut and close if configured.
If :py:attr:`autocut <escpos.escpos.EscposIO.autocut>` is `True` (set by this class' constructor), If :py:attr:`autocut <escpos.escpos.EscposIO.autocut>` is `True` (set by this class' constructor),

View File

@ -26,6 +26,8 @@ Result/Exit codes:
:license: MIT :license: MIT
""" """
from typing import Optional
class Error(Exception): class Error(Exception):
"""Base class for ESC/POS errors. """Base class for ESC/POS errors.
@ -37,7 +39,7 @@ class Error(Exception):
""" """
def __init__(self, msg, status=None): def __init__(self, msg: str, status: Optional[int] = None) -> None:
"""Initialize Error object.""" """Initialize Error object."""
Exception.__init__(self) Exception.__init__(self)
self.msg = msg self.msg = msg
@ -45,7 +47,7 @@ class Error(Exception):
if status is not None: if status is not None:
self.resultcode = status self.resultcode = status
def __str__(self): def __str__(self) -> str:
"""Return string representation of Error.""" """Return string representation of Error."""
return self.msg return self.msg
@ -64,13 +66,13 @@ class BarcodeTypeError(Error):
""" """
def __init__(self, msg=""): def __init__(self, msg: str = "") -> None:
"""Initialize BarcodeTypeError object.""" """Initialize BarcodeTypeError object."""
Error.__init__(self, msg) Error.__init__(self, msg)
self.msg = msg self.msg = msg
self.resultcode = 10 self.resultcode = 10
def __str__(self): def __str__(self) -> str:
"""Return string representation of BarcodeTypeError.""" """Return string representation of BarcodeTypeError."""
return f"No Barcode type is defined ({self.msg})" return f"No Barcode type is defined ({self.msg})"
@ -89,13 +91,13 @@ class BarcodeSizeError(Error):
""" """
def __init__(self, msg=""): def __init__(self, msg: str = "") -> None:
"""Initialize BarcodeSizeError object.""" """Initialize BarcodeSizeError object."""
Error.__init__(self, msg) Error.__init__(self, msg)
self.msg = msg self.msg = msg
self.resultcode = 20 self.resultcode = 20
def __str__(self): def __str__(self) -> str:
"""Return string representation of BarcodeSizeError.""" """Return string representation of BarcodeSizeError."""
return f"Barcode size is out of range ({self.msg})" return f"Barcode size is out of range ({self.msg})"
@ -114,13 +116,13 @@ class BarcodeCodeError(Error):
""" """
def __init__(self, msg=""): def __init__(self, msg: str = "") -> None:
"""Initialize BarcodeCodeError object.""" """Initialize BarcodeCodeError object."""
Error.__init__(self, msg) Error.__init__(self, msg)
self.msg = msg self.msg = msg
self.resultcode = 30 self.resultcode = 30
def __str__(self): def __str__(self) -> str:
"""Return string representation of BarcodeCodeError.""" """Return string representation of BarcodeCodeError."""
return f"No Barcode code was supplied ({self.msg})" return f"No Barcode code was supplied ({self.msg})"
@ -137,13 +139,13 @@ class ImageSizeError(Error):
""" """
def __init__(self, msg=""): def __init__(self, msg: str = "") -> None:
"""Initialize ImageSizeError object.""" """Initialize ImageSizeError object."""
Error.__init__(self, msg) Error.__init__(self, msg)
self.msg = msg self.msg = msg
self.resultcode = 40 self.resultcode = 40
def __str__(self): def __str__(self) -> str:
"""Return string representation of ImageSizeError.""" """Return string representation of ImageSizeError."""
return f"Image height is longer than 255px and can't be printed ({self.msg})" return f"Image height is longer than 255px and can't be printed ({self.msg})"
@ -160,13 +162,13 @@ class ImageWidthError(Error):
""" """
def __init__(self, msg=""): def __init__(self, msg: str = "") -> None:
"""Initialize ImageWidthError object.""" """Initialize ImageWidthError object."""
Error.__init__(self, msg) Error.__init__(self, msg)
self.msg = msg self.msg = msg
self.resultcode = 41 self.resultcode = 41
def __str__(self): def __str__(self) -> str:
"""Return string representation of ImageWidthError.""" """Return string representation of ImageWidthError."""
return f"Image width is too large ({self.msg})" return f"Image width is too large ({self.msg})"
@ -184,13 +186,13 @@ class TextError(Error):
""" """
def __init__(self, msg=""): def __init__(self, msg: str = "") -> None:
"""Initialize TextError object.""" """Initialize TextError object."""
Error.__init__(self, msg) Error.__init__(self, msg)
self.msg = msg self.msg = msg
self.resultcode = 50 self.resultcode = 50
def __str__(self): def __str__(self) -> str:
"""Return string representation of TextError.""" """Return string representation of TextError."""
return f"Text string must be supplied to the text() method ({self.msg})" return f"Text string must be supplied to the text() method ({self.msg})"
@ -208,13 +210,13 @@ class CashDrawerError(Error):
""" """
def __init__(self, msg=""): def __init__(self, msg: str = "") -> None:
"""Initialize CashDrawerError object.""" """Initialize CashDrawerError object."""
Error.__init__(self, msg) Error.__init__(self, msg)
self.msg = msg self.msg = msg
self.resultcode = 60 self.resultcode = 60
def __str__(self): def __str__(self) -> str:
"""Return string representation of CashDrawerError.""" """Return string representation of CashDrawerError."""
return f"Valid pin must be set to send pulse ({self.msg})" return f"Valid pin must be set to send pulse ({self.msg})"
@ -235,13 +237,13 @@ class TabPosError(Error):
""" """
def __init__(self, msg=""): def __init__(self, msg: str = "") -> None:
"""Initialize TabPosError object.""" """Initialize TabPosError object."""
Error.__init__(self, msg) Error.__init__(self, msg)
self.msg = msg self.msg = msg
self.resultcode = 70 self.resultcode = 70
def __str__(self): def __str__(self) -> str:
"""Return string representation of TabPosError.""" """Return string representation of TabPosError."""
return f"Valid tab positions must be in the range 0 to 16 ({self.msg})" return f"Valid tab positions must be in the range 0 to 16 ({self.msg})"
@ -259,13 +261,13 @@ class CharCodeError(Error):
""" """
def __init__(self, msg=""): def __init__(self, msg: str = "") -> None:
"""Initialize CharCodeError object.""" """Initialize CharCodeError object."""
Error.__init__(self, msg) Error.__init__(self, msg)
self.msg = msg self.msg = msg
self.resultcode = 80 self.resultcode = 80
def __str__(self): def __str__(self) -> str:
"""Return string representation of CharCodeError.""" """Return string representation of CharCodeError."""
return f"Valid char code must be set ({self.msg})" return f"Valid char code must be set ({self.msg})"
@ -283,13 +285,13 @@ class DeviceNotFoundError(Error):
""" """
def __init__(self, msg=""): def __init__(self, msg: str = "") -> None:
"""Initialize DeviceNotFoundError object.""" """Initialize DeviceNotFoundError object."""
Error.__init__(self, msg) Error.__init__(self, msg)
self.msg = msg self.msg = msg
self.resultcode = 90 self.resultcode = 90
def __str__(self): def __str__(self) -> str:
"""Return string representation of DeviceNotFoundError.""" """Return string representation of DeviceNotFoundError."""
return f"Device not found ({self.msg})" return f"Device not found ({self.msg})"
@ -307,13 +309,13 @@ class USBNotFoundError(DeviceNotFoundError):
""" """
def __init__(self, msg=""): def __init__(self, msg: str = "") -> None:
"""Initialize USBNotFoundError object.""" """Initialize USBNotFoundError object."""
Error.__init__(self, msg) Error.__init__(self, msg)
self.msg = msg self.msg = msg
self.resultcode = 91 self.resultcode = 91
def __str__(self): def __str__(self) -> str:
"""Return string representation of USBNotFoundError.""" """Return string representation of USBNotFoundError."""
return f"USB device not found ({self.msg})" return f"USB device not found ({self.msg})"
@ -331,13 +333,13 @@ class SetVariableError(Error):
""" """
def __init__(self, msg=""): def __init__(self, msg: str = "") -> None:
"""Initialize SetVariableError object.""" """Initialize SetVariableError object."""
Error.__init__(self, msg) Error.__init__(self, msg)
self.msg = msg self.msg = msg
self.resultcode = 100 self.resultcode = 100
def __str__(self): def __str__(self) -> str:
"""Return string representation of SetVariableError.""" """Return string representation of SetVariableError."""
return f"Set variable out of range ({self.msg})" return f"Set variable out of range ({self.msg})"
@ -358,13 +360,13 @@ class ConfigNotFoundError(Error):
""" """
def __init__(self, msg=""): def __init__(self, msg: str = "") -> None:
"""Initialize ConfigNotFoundError object.""" """Initialize ConfigNotFoundError object."""
Error.__init__(self, msg) Error.__init__(self, msg)
self.msg = msg self.msg = msg
self.resultcode = 200 self.resultcode = 200
def __str__(self): def __str__(self) -> str:
"""Return string representation of ConfigNotFoundError.""" """Return string representation of ConfigNotFoundError."""
return f"Configuration not found ({self.msg})" return f"Configuration not found ({self.msg})"
@ -382,13 +384,13 @@ class ConfigSyntaxError(Error):
""" """
def __init__(self, msg=""): def __init__(self, msg: str = "") -> None:
"""Initialize ConfigSyntaxError object.""" """Initialize ConfigSyntaxError object."""
Error.__init__(self, msg) Error.__init__(self, msg)
self.msg = msg self.msg = msg
self.resultcode = 210 self.resultcode = 210
def __str__(self): def __str__(self) -> str:
"""Return string representation of ConfigSyntaxError.""" """Return string representation of ConfigSyntaxError."""
return f"Configuration syntax is invalid ({self.msg})" return f"Configuration syntax is invalid ({self.msg})"
@ -406,12 +408,12 @@ class ConfigSectionMissingError(Error):
""" """
def __init__(self, msg=""): def __init__(self, msg: str = "") -> None:
"""Initialize ConfigSectionMissingError object.""" """Initialize ConfigSectionMissingError object."""
Error.__init__(self, msg) Error.__init__(self, msg)
self.msg = msg self.msg = msg
self.resultcode = 220 self.resultcode = 220
def __str__(self): def __str__(self) -> str:
"""Return string representation of ConfigSectionMissingError.""" """Return string representation of ConfigSectionMissingError."""
return f"Configuration section is missing ({self.msg})" return f"Configuration section is missing ({self.msg})"

View File

@ -10,12 +10,12 @@ This module contains the image format handler :py:class:`EscposImage`.
import math import math
from typing import Union from typing import Iterator, Union
from PIL import Image, ImageOps from PIL import Image, ImageOps
class EscposImage(object): class EscposImage:
""" """
Load images in, and output ESC/POS formats. Load images in, and output ESC/POS formats.
@ -23,7 +23,7 @@ class EscposImage(object):
PIL, rather than spend CPU cycles looping over pixels. PIL, rather than spend CPU cycles looping over pixels.
""" """
def __init__(self, img_source: Union[Image.Image, str]): def __init__(self, img_source: Union[Image.Image, str]) -> None:
"""Load in an image. """Load in an image.
:param img_source: PIL.Image, or filename to load one from. :param img_source: PIL.Image, or filename to load one from.
@ -65,7 +65,7 @@ class EscposImage(object):
_, height_pixels = self._im.size _, height_pixels = self._im.size
return height_pixels return height_pixels
def to_column_format(self, high_density_vertical: bool = True): def to_column_format(self, high_density_vertical: bool = True) -> Iterator[bytes]:
"""Extract slices of an image as equal-sized blobs of column-format data. """Extract slices of an image as equal-sized blobs of column-format data.
:param high_density_vertical: Printed line height in dots :param high_density_vertical: Printed line height in dots
@ -82,7 +82,7 @@ class EscposImage(object):
yield (im_bytes) yield (im_bytes)
left += line_height left += line_height
def to_raster_format(self): def to_raster_format(self) -> bytes:
"""Convert image to raster-format binary.""" """Convert image to raster-format binary."""
return self._im.tobytes() return self._im.tobytes()

View File

@ -11,7 +11,7 @@ except ImportError:
jaconv = None jaconv = None
def encode_katakana(text): def encode_katakana(text: str) -> bytes:
"""I don't think this quite works yet.""" """I don't think this quite works yet."""
encoded = [] encoded = []
for char in text: for char in text:

View File

@ -23,7 +23,7 @@ from .constants import CODEPAGE_CHANGE
from .exceptions import Error from .exceptions import Error
class Encoder(object): class Encoder:
"""Take available code spaces and pick the right one for a given character. """Take available code spaces and pick the right one for a given character.
Note: To determine the code page, it needs to do the conversion, and Note: To determine the code page, it needs to do the conversion, and
@ -202,7 +202,7 @@ def split_writable_text(encoder, text, encoding):
return text, None return text, None
class MagicEncode(object): class MagicEncode:
"""Help switching to the right code page. """Help switching to the right code page.
A helper that helps us to automatically switch to the right A helper that helps us to automatically switch to the right
@ -292,7 +292,7 @@ class MagicEncode(object):
def write_with_encoding(self, encoding, text): def write_with_encoding(self, encoding, text):
"""Write the text and inject necessary codepage switches.""" """Write the text and inject necessary codepage switches."""
if text is not None and type(text) is not six.text_type: if text is not None and type(text) is not str:
raise Error( raise Error(
f"The supplied text has to be unicode, but is of type {type(text)}." f"The supplied text has to be unicode, but is of type {type(text)}."
) )

View File

@ -84,7 +84,7 @@ class CupsPrinter(Escpos):
return is_usable() return is_usable()
@dependency_pycups @dependency_pycups
def __init__(self, printer_name: str = "", *args, **kwargs): def __init__(self, printer_name: str = "", *args, **kwargs) -> None:
"""Class constructor for CupsPrinter. """Class constructor for CupsPrinter.
:param printer_name: CUPS printer name (Optional) :param printer_name: CUPS printer name (Optional)
@ -163,11 +163,10 @@ class CupsPrinter(Escpos):
return return
logging.info("CupsPrinter printer enabled") logging.info("CupsPrinter printer enabled")
def _raw(self, msg): def _raw(self, msg: bytes) -> None:
"""Append any command sent in raw format to temporary file. """Append any command sent in raw format to temporary file.
:param msg: arbitrary code to be printed :param msg: arbitrary code to be printed
:type msg: bytes
""" """
self.pending_job = True self.pending_job = True
try: try:
@ -176,8 +175,9 @@ class CupsPrinter(Escpos):
self.pending_job = False self.pending_job = False
raise TypeError("Bytes required. Printer job not opened") raise TypeError("Bytes required. Printer job not opened")
def send(self): def send(self) -> None:
"""Send the print job to the printer.""" """Send the print job to the printer."""
assert self.device
if self.pending_job: if self.pending_job:
# Rewind tempfile # Rewind tempfile
self.tmpfile.seek(0) self.tmpfile.seek(0)
@ -190,7 +190,7 @@ class CupsPrinter(Escpos):
) )
self._clear() self._clear()
def _clear(self): def _clear(self) -> None:
"""Finish the print job. """Finish the print job.
Remove temporary file. Remove temporary file.
@ -198,18 +198,18 @@ class CupsPrinter(Escpos):
self.tmpfile.close() self.tmpfile.close()
self.pending_job = False self.pending_job = False
def _read(self): def _read(self) -> bytes:
"""Return a single-item array with the accepting state of the print queue. """Return a single-item array with the accepting state of the print queue.
states: idle = [3], printing a job = [4], stopped = [5] states: idle = [3], printing a job = [4], stopped = [5]
""" """
printer = self.printers.get(self.printer_name, {}) printer = self.printers.get(self.printer_name, {})
state = printer.get("printer-state") state = printer.get("printer-state")
if not state: if not state or state in [4, 5]:
return [] return b"8" # offline
return [state] return b"0" # online
def close(self): def close(self) -> None:
"""Close CUPS connection. """Close CUPS connection.
Send pending job to the printer if needed. Send pending job to the printer if needed.

View File

@ -7,6 +7,7 @@
:copyright: Copyright (c) 2012-2023 Bashlinux and python-escpos :copyright: Copyright (c) 2012-2023 Bashlinux and python-escpos
:license: MIT :license: MIT
""" """
from typing import List
from ..escpos import Escpos from ..escpos import Escpos
@ -39,25 +40,24 @@ class Dummy(Escpos):
""" """
return is_usable() return is_usable()
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs) -> None:
"""Init with empty output list.""" """Init with empty output list."""
Escpos.__init__(self, *args, **kwargs) Escpos.__init__(self, *args, **kwargs)
self._output_list = [] self._output_list: List[bytes] = []
def _raw(self, msg): def _raw(self, msg: bytes) -> None:
"""Print any command sent in raw format. """Print any command sent in raw format.
:param msg: arbitrary code to be printed :param msg: arbitrary code to be printed
:type msg: bytes
""" """
self._output_list.append(msg) self._output_list.append(msg)
@property @property
def output(self): def output(self) -> bytes:
"""Get the data that was sent to this printer.""" """Get the data that was sent to this printer."""
return b"".join(self._output_list) return b"".join(self._output_list)
def clear(self): def clear(self) -> None:
"""Clear the buffer of the printer. """Clear the buffer of the printer.
This method can be called if you send the contents to a physical printer This method can be called if you send the contents to a physical printer
@ -65,6 +65,6 @@ class Dummy(Escpos):
""" """
del self._output_list[:] del self._output_list[:]
def close(self): def close(self) -> None:
"""Close not implemented for Dummy printer.""" """Close not implemented for Dummy printer."""
pass pass

View File

@ -88,12 +88,12 @@ class File(Escpos):
if self.device: if self.device:
self.device.flush() self.device.flush()
def _raw(self, msg): def _raw(self, msg: bytes) -> None:
"""Print any command sent in raw format. """Print any command sent in raw format.
:param msg: arbitrary code to be printed :param msg: arbitrary code to be printed
:type msg: bytes
""" """
assert self.device
self.device.write(msg) self.device.write(msg)
if self.auto_flush: if self.auto_flush:
self.flush() self.flush()

View File

@ -182,12 +182,13 @@ class LP(Escpos):
if not self._is_closing: if not self._is_closing:
self.open(_close_opened=False) self.open(_close_opened=False)
def _raw(self, msg): def _raw(self, msg: bytes) -> None:
"""Write raw command(s) to the printer. """Write raw command(s) to the printer.
:param msg: arbitrary code to be printed :param msg: arbitrary code to be printed
:type msg: bytes
""" """
assert self.device is not None
assert self.device.stdin is not None
if self.device.stdin.writable(): if self.device.stdin.writable():
self.device.stdin.write(msg) self.device.stdin.write(msg)
else: else:

View File

@ -110,16 +110,17 @@ class Network(Escpos):
return return
logging.info("Network printer enabled") logging.info("Network printer enabled")
def _raw(self, msg): def _raw(self, msg: bytes) -> None:
"""Print any command sent in raw format. """Print any command sent in raw format.
:param msg: arbitrary code to be printed :param msg: arbitrary code to be printed
:type msg: bytes
""" """
assert self.device
self.device.sendall(msg) self.device.sendall(msg)
def _read(self): def _read(self) -> bytes:
"""Read data from the TCP socket.""" """Read data from the TCP socket."""
assert self.device
return self.device.recv(16) return self.device.recv(16)
def close(self) -> None: def close(self) -> None:

View File

@ -155,16 +155,17 @@ class Serial(Escpos):
return return
logging.info("Serial printer enabled") logging.info("Serial printer enabled")
def _raw(self, msg): def _raw(self, msg: bytes) -> None:
"""Print any command sent in raw format. """Print any command sent in raw format.
:param msg: arbitrary code to be printed :param msg: arbitrary code to be printed
:type msg: bytes
""" """
assert self.device
self.device.write(msg) self.device.write(msg)
def _read(self): def _read(self) -> bytes:
"""Read the data buffer and return it to the caller.""" """Read the data buffer and return it to the caller."""
assert self.device
return self.device.read(16) return self.device.read(16)
def close(self) -> None: def close(self) -> None:

View File

@ -74,9 +74,9 @@ class Usb(Escpos):
def __init__( def __init__(
self, self,
idVendor: str = "", idVendor: Optional[int] = None,
idProduct: str = "", idProduct: Optional[int] = None,
usb_args: Dict[str, str] = {}, usb_args: Dict[str, Union[str, int]] = {},
timeout: Union[int, float] = 0, timeout: Union[int, float] = 0,
in_ep: int = 0x82, in_ep: int = 0x82,
out_ep: int = 0x01, out_ep: int = 0x01,
@ -181,16 +181,17 @@ class Usb(Escpos):
except usb.core.USBError as e: except usb.core.USBError as e:
logging.error("Could not set configuration: %s", str(e)) logging.error("Could not set configuration: %s", str(e))
def _raw(self, msg): def _raw(self, msg: bytes) -> None:
"""Print any command sent in raw format. """Print any command sent in raw format.
:param msg: arbitrary code to be printed :param msg: arbitrary code to be printed
:type msg: bytes
""" """
assert self.device
self.device.write(self.out_ep, msg, self.timeout) self.device.write(self.out_ep, msg, self.timeout)
def _read(self): def _read(self) -> bytes:
"""Read a data buffer and return it to the caller.""" """Read a data buffer and return it to the caller."""
assert self.device
return self.device.read(self.in_ep, 16) return self.device.read(self.in_ep, 16)
@dependency_usb @dependency_usb

View File

@ -76,7 +76,7 @@ class Win32Raw(Escpos):
return is_usable() return is_usable()
@dependency_win32print @dependency_win32print
def __init__(self, printer_name: str = "", *args, **kwargs): def __init__(self, printer_name: str = "", *args, **kwargs) -> None:
"""Initialize default printer.""" """Initialize default printer."""
Escpos.__init__(self, *args, **kwargs) Escpos.__init__(self, *args, **kwargs)
self.printer_name = printer_name self.printer_name = printer_name
@ -148,14 +148,17 @@ class Win32Raw(Escpos):
win32print.ClosePrinter(self._device) win32print.ClosePrinter(self._device)
self._device = False self._device = False
def _raw(self, msg) -> None: def _raw(self, msg: bytes) -> None:
"""Print any command sent in raw format. """Print any command sent in raw format.
:param msg: arbitrary code to be printed :param msg: arbitrary code to be printed
:type msg: bytes
""" """
if self.printer_name is None: if self.printer_name is None:
raise DeviceNotFoundError("Printer not found") raise DeviceNotFoundError("Printer not found")
if not self.device: if not self.device:
raise DeviceNotFoundError("Printer job not opened") raise DeviceNotFoundError("Printer job not opened")
win32print.WritePrinter(self.device, msg) win32print.WritePrinter(self.device, msg) # type: ignore
# there is a bug in the typeshed
# https://github.com/mhammond/pywin32/blob/main/win32/src/win32print/win32print.cpp#L976
# https://github.com/python/typeshed/blob/main/stubs/pywin32/win32/win32print.pyi#L27C4-L27C4

View File

@ -7,7 +7,6 @@ import os
import shutil import shutil
import tempfile import tempfile
import pytest
from scripttest import TestFileEnvironment as TFE from scripttest import TestFileEnvironment as TFE
import escpos import escpos
@ -31,22 +30,19 @@ class TestCLI:
"""Contains setups, teardowns, and tests for CLI""" """Contains setups, teardowns, and tests for CLI"""
@classmethod @classmethod
def setup_class(cls): def setup_class(cls) -> None:
"""Create a config file to read from""" """Create a config file to read from"""
with open(CONFIGFILE, "w") as config: with open(CONFIGFILE, "w") as config:
config.write(CONFIG_YAML) config.write(CONFIG_YAML)
@classmethod @classmethod
def teardown_class(cls): def teardown_class(cls) -> None:
"""Remove config file""" """Remove config file"""
os.remove(CONFIGFILE) os.remove(CONFIGFILE)
shutil.rmtree(TEST_DIR) shutil.rmtree(TEST_DIR)
def setup_method(self): def setup_method(self) -> None:
"""Create a file to print to and set up env""" """Create a file to print to and set up env"""
self.env = None
self.default_args = None
self.env = TFE( self.env = TFE(
base_path=TEST_DIR, base_path=TEST_DIR,
cwd=os.getcwd(), cwd=os.getcwd(),
@ -64,24 +60,24 @@ class TestCLI:
finally: finally:
fhandle.close() fhandle.close()
def teardown_method(self): def teardown_method(self) -> None:
"""Destroy printer file and env""" """Destroy printer file and env"""
os.remove(DEVFILE) os.remove(DEVFILE)
self.env.clear() self.env.clear()
def test_cli_help(self): def test_cli_help(self) -> None:
"""Test getting help from cli""" """Test getting help from cli"""
result = self.env.run("python-escpos", "-h") result = self.env.run("python-escpos", "-h")
assert not result.stderr assert not result.stderr
assert "usage" in result.stdout assert "usage" in result.stdout
def test_cli_version(self): def test_cli_version(self) -> None:
"""Test the version string""" """Test the version string"""
result = self.env.run("python-escpos", "version") result = self.env.run("python-escpos", "version")
assert not result.stderr assert not result.stderr
assert escpos.__version__ == result.stdout.strip() assert escpos.__version__ == result.stdout.strip()
def test_cli_version_extended(self): def test_cli_version_extended(self) -> None:
"""Test the extended version information""" """Test the extended version information"""
result = self.env.run("python-escpos", "version_extended") result = self.env.run("python-escpos", "version_extended")
assert not result.stderr assert not result.stderr
@ -89,7 +85,7 @@ class TestCLI:
# test that additional information on e.g. Serial is printed # test that additional information on e.g. Serial is printed
assert "Serial" in result.stdout assert "Serial" in result.stdout
def test_cli_text(self): def test_cli_text(self) -> None:
"""Make sure text returns what we sent it""" """Make sure text returns what we sent it"""
test_text = "this is some text" test_text = "this is some text"
result = self.env.run( result = self.env.run(
@ -108,7 +104,7 @@ class TestCLI:
result.files_updated[DEVFILE_NAME].bytes == "\x1bt\x00" + test_text + "\n" result.files_updated[DEVFILE_NAME].bytes == "\x1bt\x00" + test_text + "\n"
) )
def test_cli_text_invalid_args(self): def test_cli_text_invalid_args(self) -> None:
"""Test a failure to send valid arguments""" """Test a failure to send valid arguments"""
result = self.env.run( result = self.env.run(
*(self.default_args + ("text", "--invalid-param", "some data")), *(self.default_args + ("text", "--invalid-param", "some data")),

View File

@ -1,14 +1,13 @@
import pytest import pytest
import six
from escpos import printer from escpos import printer
from escpos.constants import BUZZER from escpos.constants import BUZZER
def test_buzzer_function_with_default_params(): def test_buzzer_function_with_default_params() -> None:
instance = printer.Dummy() instance = printer.Dummy()
instance.buzzer() instance.buzzer()
expected = BUZZER + six.int2byte(2) + six.int2byte(4) expected = BUZZER + bytes((2, 4))
assert instance.output == expected assert instance.output == expected
@ -26,10 +25,10 @@ def test_buzzer_function_with_default_params():
[9, 9], [9, 9],
], ],
) )
def test_buzzer_function(times, duration): def test_buzzer_function(times: int, duration: int) -> None:
instance = printer.Dummy() instance = printer.Dummy()
instance.buzzer(times, duration) instance.buzzer(times, duration)
expected = BUZZER + six.int2byte(times) + six.int2byte(duration) expected = BUZZER + bytes((times, duration))
assert instance.output == expected assert instance.output == expected
@ -46,7 +45,9 @@ def test_buzzer_function(times, duration):
[3, 11, "duration must be between 1 and 9"], [3, 11, "duration must be between 1 and 9"],
], ],
) )
def test_buzzer_fuction_with_outrange_values(times, duration, expected_message): def test_buzzer_fuction_with_outrange_values(
times: int, duration: int, expected_message: str
) -> None:
instance = printer.Dummy() instance = printer.Dummy()
with pytest.raises(ValueError) as e: with pytest.raises(ValueError) as e:
instance.buzzer(times, duration) instance.buzzer(times, duration)

View File

@ -1,5 +1,3 @@
import six
import escpos.printer as printer import escpos.printer as printer
from escpos.constants import GS from escpos.constants import GS
@ -8,5 +6,5 @@ def test_cut_without_feed():
"""Test cut without feeding paper""" """Test cut without feeding paper"""
instance = printer.Dummy() instance = printer.Dummy()
instance.cut(feed=False) instance.cut(feed=False)
expected = GS + b"V" + six.int2byte(66) + b"\x00" expected = GS + b"V" + bytes((66,)) + b"\x00"
assert instance.output == expected assert instance.output == expected

View File

@ -16,7 +16,7 @@ from escpos.exceptions import ImageWidthError
# Raster format print # Raster format print
def test_bit_image_black(): def test_bit_image_black() -> None:
""" """
Test printing solid black bit image (raster) Test printing solid black bit image (raster)
""" """
@ -30,7 +30,7 @@ def test_bit_image_black():
assert instance.output == b"\x1dv0\x00\x01\x00\x01\x00\x80" assert instance.output == b"\x1dv0\x00\x01\x00\x01\x00\x80"
def test_bit_image_white(): def test_bit_image_white() -> None:
""" """
Test printing solid white bit image (raster) Test printing solid white bit image (raster)
""" """
@ -39,7 +39,7 @@ def test_bit_image_white():
assert instance.output == b"\x1dv0\x00\x01\x00\x01\x00\x00" assert instance.output == b"\x1dv0\x00\x01\x00\x01\x00\x00"
def test_bit_image_both(): def test_bit_image_both() -> None:
""" """
Test printing black/white bit image (raster) Test printing black/white bit image (raster)
""" """
@ -58,7 +58,7 @@ def test_bit_image_transparent():
# Column format print # Column format print
def test_bit_image_colfmt_black(): def test_bit_image_colfmt_black() -> None:
""" """
Test printing solid black bit image (column format) Test printing solid black bit image (column format)
""" """
@ -67,7 +67,7 @@ def test_bit_image_colfmt_black():
assert instance.output == b"\x1b3\x10\x1b*!\x01\x00\x80\x00\x00\x0a\x1b2" assert instance.output == b"\x1b3\x10\x1b*!\x01\x00\x80\x00\x00\x0a\x1b2"
def test_bit_image_colfmt_white(): def test_bit_image_colfmt_white() -> None:
""" """
Test printing solid white bit image (column format) Test printing solid white bit image (column format)
""" """
@ -76,7 +76,7 @@ def test_bit_image_colfmt_white():
assert instance.output == b"\x1b3\x10\x1b*!\x01\x00\x00\x00\x00\x0a\x1b2" assert instance.output == b"\x1b3\x10\x1b*!\x01\x00\x00\x00\x00\x0a\x1b2"
def test_bit_image_colfmt_both(): def test_bit_image_colfmt_both() -> None:
""" """
Test printing black/white bit image (column format) Test printing black/white bit image (column format)
""" """
@ -87,7 +87,7 @@ def test_bit_image_colfmt_both():
) )
def test_bit_image_colfmt_transparent(): def test_bit_image_colfmt_transparent() -> None:
""" """
Test printing black/transparent bit image (column format) Test printing black/transparent bit image (column format)
""" """
@ -99,7 +99,7 @@ def test_bit_image_colfmt_transparent():
# Graphics print # Graphics print
def test_graphics_black(): def test_graphics_black() -> None:
""" """
Test printing solid black graphics Test printing solid black graphics
""" """
@ -111,7 +111,7 @@ def test_graphics_black():
) )
def test_graphics_white(): def test_graphics_white() -> None:
""" """
Test printing solid white graphics Test printing solid white graphics
""" """
@ -123,7 +123,7 @@ def test_graphics_white():
) )
def test_graphics_both(): def test_graphics_both() -> None:
""" """
Test printing black/white graphics Test printing black/white graphics
""" """
@ -135,7 +135,7 @@ def test_graphics_both():
) )
def test_graphics_transparent(): def test_graphics_transparent() -> None:
""" """
Test printing black/transparent graphics Test printing black/transparent graphics
""" """
@ -147,7 +147,7 @@ def test_graphics_transparent():
) )
def test_large_graphics(): def test_large_graphics() -> None:
""" """
Test whether 'large' graphics that induce a fragmentation are handled correctly. Test whether 'large' graphics that induce a fragmentation are handled correctly.
""" """
@ -162,13 +162,13 @@ def test_large_graphics():
@pytest.fixture @pytest.fixture
def dummy_with_width(): def dummy_with_width() -> printer.Dummy:
instance = printer.Dummy() instance = printer.Dummy()
instance.profile.profile_data = {"media": {"width": {"pixels": 384}}} instance.profile.profile_data = {"media": {"width": {"pixels": 384}}}
return instance return instance
def test_width_too_large(dummy_with_width): def test_width_too_large(dummy_with_width: printer.Dummy) -> None:
""" """
Test printing an image that is too large in width. Test printing an image that is too large in width.
""" """
@ -180,7 +180,7 @@ def test_width_too_large(dummy_with_width):
instance.image(Image.new("RGB", (384, 200))) instance.image(Image.new("RGB", (384, 200)))
def test_center_image(dummy_with_width): def test_center_image(dummy_with_width: printer.Dummy) -> None:
instance = dummy_with_width instance = dummy_with_width
with pytest.raises(ImageWidthError): with pytest.raises(ImageWidthError):

View File

@ -11,21 +11,21 @@
import escpos.printer as printer import escpos.printer as printer
def test_function_linedisplay_select_on(): def test_function_linedisplay_select_on() -> None:
"""test the linedisplay_select function (activate)""" """test the linedisplay_select function (activate)"""
instance = printer.Dummy() instance = printer.Dummy()
instance.linedisplay_select(select_display=True) instance.linedisplay_select(select_display=True)
assert instance.output == b"\x1B\x3D\x02" assert instance.output == b"\x1B\x3D\x02"
def test_function_linedisplay_select_off(): def test_function_linedisplay_select_off() -> None:
"""test the linedisplay_select function (deactivate)""" """test the linedisplay_select function (deactivate)"""
instance = printer.Dummy() instance = printer.Dummy()
instance.linedisplay_select(select_display=False) instance.linedisplay_select(select_display=False)
assert instance.output == b"\x1B\x3D\x01" assert instance.output == b"\x1B\x3D\x01"
def test_function_linedisplay_clear(): def test_function_linedisplay_clear() -> None:
"""test the linedisplay_clear function""" """test the linedisplay_clear function"""
instance = printer.Dummy() instance = printer.Dummy()
instance.linedisplay_clear() instance.linedisplay_clear()

View File

@ -1,3 +1,5 @@
from typing import Optional
import pytest import pytest
import six import six
@ -6,7 +8,7 @@ from escpos.constants import SET_FONT, TXT_NORMAL, TXT_SIZE, TXT_STYLE
from escpos.exceptions import SetVariableError from escpos.exceptions import SetVariableError
def test_default_values_with_default(): def test_default_values_with_default() -> None:
"""Default test, please copy and paste this block to test set method calls""" """Default test, please copy and paste this block to test set method calls"""
instance = printer.Dummy() instance = printer.Dummy()
instance.set_with_default() instance.set_with_default()
@ -26,7 +28,7 @@ def test_default_values_with_default():
assert instance.output == b"".join(expected_sequence) assert instance.output == b"".join(expected_sequence)
def test_default_values(): def test_default_values() -> None:
"""Default test""" """Default test"""
instance = printer.Dummy() instance = printer.Dummy()
instance.set() instance.set()
@ -37,7 +39,7 @@ def test_default_values():
# Size tests # Size tests
def test_set_size_2h(): def test_set_size_2h() -> None:
instance = printer.Dummy() instance = printer.Dummy()
instance.set_with_default(double_height=True) instance.set_with_default(double_height=True)
@ -56,7 +58,7 @@ 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(): def test_set_size_2h_no_default() -> None:
instance = printer.Dummy() instance = printer.Dummy()
instance.set(double_height=True) instance.set(double_height=True)
@ -68,7 +70,7 @@ def test_set_size_2h_no_default():
assert instance.output == b"".join(expected_sequence) assert instance.output == b"".join(expected_sequence)
def test_set_size_2w(): def test_set_size_2w() -> None:
instance = printer.Dummy() instance = printer.Dummy()
instance.set_with_default(double_width=True) instance.set_with_default(double_width=True)
@ -87,7 +89,7 @@ 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(): def test_set_size_2w_no_default() -> None:
instance = printer.Dummy() instance = printer.Dummy()
instance.set(double_width=True) instance.set(double_width=True)
@ -99,7 +101,7 @@ def test_set_size_2w_no_default():
assert instance.output == b"".join(expected_sequence) assert instance.output == b"".join(expected_sequence)
def test_set_size_2x(): def test_set_size_2x() -> None:
instance = printer.Dummy() instance = printer.Dummy()
instance.set_with_default(double_height=True, double_width=True) instance.set_with_default(double_height=True, double_width=True)
@ -118,7 +120,7 @@ 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(): def test_set_size_2x_no_default() -> None:
instance = printer.Dummy() instance = printer.Dummy()
instance.set(double_width=True, double_height=True) instance.set(double_width=True, double_height=True)
@ -130,7 +132,7 @@ def test_set_size_2x_no_default():
assert instance.output == b"".join(expected_sequence) assert instance.output == b"".join(expected_sequence)
def test_set_size_custom(): def test_set_size_custom() -> None:
instance = printer.Dummy() instance = printer.Dummy()
instance.set_with_default(custom_size=True, width=8, height=7) instance.set_with_default(custom_size=True, width=8, height=7)
@ -151,7 +153,7 @@ def test_set_size_custom():
@pytest.mark.parametrize("width", [1, 8]) @pytest.mark.parametrize("width", [1, 8])
@pytest.mark.parametrize("height", [1, 8]) @pytest.mark.parametrize("height", [1, 8])
def test_set_size_custom_no_default(width, height): def test_set_size_custom_no_default(width: int, height: int) -> None:
instance = printer.Dummy() instance = printer.Dummy()
instance.set(custom_size=True, width=width, height=height) instance.set(custom_size=True, width=width, height=height)
@ -165,7 +167,9 @@ def test_set_size_custom_no_default(width, height):
@pytest.mark.parametrize("width", [None, 0, 9, 10, 4444]) @pytest.mark.parametrize("width", [None, 0, 9, 10, 4444])
@pytest.mark.parametrize("height", [None, 0, 9, 10, 4444]) @pytest.mark.parametrize("height", [None, 0, 9, 10, 4444])
def test_set_size_custom_invalid_input(width, height): def test_set_size_custom_invalid_input(
width: Optional[int], height: Optional[int]
) -> None:
instance = printer.Dummy() instance = printer.Dummy()
with pytest.raises(SetVariableError): with pytest.raises(SetVariableError):
instance.set(custom_size=True, width=width, height=height) instance.set(custom_size=True, width=width, height=height)
@ -174,7 +178,7 @@ def test_set_size_custom_invalid_input(width, height):
# Flip # Flip
def test_set_flip(): def test_set_flip() -> None:
instance = printer.Dummy() instance = printer.Dummy()
instance.set_with_default(flip=True) instance.set_with_default(flip=True)
@ -193,7 +197,7 @@ def test_set_flip():
assert instance.output == b"".join(expected_sequence) assert instance.output == b"".join(expected_sequence)
def test_set_flip_no_default(): def test_set_flip_no_default() -> None:
instance = printer.Dummy() instance = printer.Dummy()
instance.set(flip=True) instance.set(flip=True)
@ -205,7 +209,7 @@ def test_set_flip_no_default():
# Smooth # Smooth
def test_smooth(): def test_smooth() -> None:
instance = printer.Dummy() instance = printer.Dummy()
instance.set_with_default(smooth=True) instance.set_with_default(smooth=True)
@ -227,7 +231,7 @@ def test_smooth():
# Type # Type
def test_set_bold(): def test_set_bold() -> None:
instance = printer.Dummy() instance = printer.Dummy()
instance.set_with_default(bold=True) instance.set_with_default(bold=True)
@ -246,7 +250,7 @@ def test_set_bold():
assert instance.output == b"".join(expected_sequence) assert instance.output == b"".join(expected_sequence)
def test_set_underline(): def test_set_underline() -> None:
instance = printer.Dummy() instance = printer.Dummy()
instance.set_with_default(underline=1) instance.set_with_default(underline=1)
@ -265,7 +269,7 @@ def test_set_underline():
assert instance.output == b"".join(expected_sequence) assert instance.output == b"".join(expected_sequence)
def test_set_underline2(): def test_set_underline2() -> None:
instance = printer.Dummy() instance = printer.Dummy()
instance.set_with_default(underline=2) instance.set_with_default(underline=2)
@ -287,7 +291,7 @@ def test_set_underline2():
# Align # Align
def test_align_center(): def test_align_center() -> None:
instance = printer.Dummy() instance = printer.Dummy()
instance.set_with_default(align="center") instance.set_with_default(align="center")
@ -306,7 +310,7 @@ def test_align_center():
assert instance.output == b"".join(expected_sequence) assert instance.output == b"".join(expected_sequence)
def test_align_right(): def test_align_right() -> None:
instance = printer.Dummy() instance = printer.Dummy()
instance.set_with_default(align="right") instance.set_with_default(align="right")
@ -328,7 +332,7 @@ def test_align_right():
# Densities # Densities
def test_densities(): def test_densities() -> None:
for density in range(8): for density in range(8):
instance = printer.Dummy() instance = printer.Dummy()
instance.set_with_default(density=density) instance.set_with_default(density=density)
@ -352,7 +356,7 @@ def test_densities():
# Invert # Invert
def test_invert(): def test_invert() -> None:
instance = printer.Dummy() instance = printer.Dummy()
instance.set_with_default(invert=True) instance.set_with_default(invert=True)

View File

@ -10,8 +10,7 @@
import hypothesis.strategies as st import hypothesis.strategies as st
import mock import mock
import pytest from hypothesis import given
from hypothesis import assume, given
from escpos.printer import Dummy from escpos.printer import Dummy
@ -21,7 +20,7 @@ def get_printer():
@given(text=st.text()) @given(text=st.text())
def test_text(text): def test_text(text: str) -> None:
"""Test that text() calls the MagicEncode object.""" """Test that text() calls the MagicEncode object."""
instance = get_printer() instance = get_printer()
instance.magic.write = mock.Mock() instance.magic.write = mock.Mock()
@ -29,7 +28,7 @@ def test_text(text):
instance.magic.write.assert_called_with(text) instance.magic.write.assert_called_with(text)
def test_block_text(): def test_block_text() -> None:
printer = get_printer() printer = get_printer()
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"
@ -39,25 +38,25 @@ def test_block_text():
) )
def test_textln(): def test_textln() -> None:
printer = get_printer() printer = get_printer()
printer.textln("hello, world") printer.textln("hello, world")
assert printer.output == b"hello, world\n" assert printer.output == b"hello, world\n"
def test_textln_empty(): def test_textln_empty() -> None:
printer = get_printer() printer = get_printer()
printer.textln() printer.textln()
assert printer.output == b"\n" assert printer.output == b"\n"
def test_ln(): def test_ln() -> None:
printer = get_printer() printer = get_printer()
printer.ln() printer.ln()
assert printer.output == b"\n" assert printer.output == b"\n"
def test_multiple_ln(): def test_multiple_ln() -> None:
printer = get_printer() printer = get_printer()
printer.ln(3) printer.ln(3)
assert printer.output == b"\n\n\n" assert printer.output == b"\n\n\n"

View File

@ -7,11 +7,12 @@ converted to ESC/POS column & raster formats.
:copyright: Copyright (c) 2016 `Michael Billington <michael.billington@gmail.com>`_ :copyright: Copyright (c) 2016 `Michael Billington <michael.billington@gmail.com>`_
:license: MIT :license: MIT
""" """
from typing import List
from escpos.image import EscposImage from escpos.image import EscposImage
def test_image_black(): def test_image_black() -> None:
""" """
Test rendering solid black image Test rendering solid black image
""" """
@ -19,7 +20,7 @@ def test_image_black():
_load_and_check_img("canvas_black." + img_format, 1, 1, b"\x80", [b"\x80"]) _load_and_check_img("canvas_black." + img_format, 1, 1, b"\x80", [b"\x80"])
def test_image_black_transparent(): def test_image_black_transparent() -> None:
""" """
Test rendering black/transparent image Test rendering black/transparent image
""" """
@ -29,7 +30,7 @@ def test_image_black_transparent():
) )
def test_image_black_white(): def test_image_black_white() -> None:
""" """
Test rendering black/white image Test rendering black/white image
""" """
@ -39,7 +40,7 @@ def test_image_black_white():
) )
def test_image_white(): def test_image_white() -> None:
""" """
Test rendering solid white image Test rendering solid white image
""" """
@ -47,7 +48,7 @@ def test_image_white():
_load_and_check_img("canvas_white." + img_format, 1, 1, b"\x00", [b"\x00"]) _load_and_check_img("canvas_white." + img_format, 1, 1, b"\x00", [b"\x00"])
def test_split(): def test_split() -> None:
""" """
test whether the split-function works as expected test whether the split-function works as expected
""" """
@ -62,12 +63,12 @@ def test_split():
def _load_and_check_img( def _load_and_check_img(
filename, filename: str,
width_expected, width_expected: int,
height_expected, height_expected: int,
raster_format_expected, raster_format_expected: bytes,
column_format_expected, column_format_expected: List[bytes],
): ) -> None:
""" """
Load an image, and test whether raster & column formatted output, sizes, etc match expectations. Load an image, and test whether raster & column formatted output, sizes, etc match expectations.
""" """

View File

@ -13,7 +13,7 @@ import hypothesis.strategies as st
import pytest import pytest
from hypothesis import example, given from hypothesis import example, given
from escpos.exceptions import CharCodeError, Error from escpos.exceptions import Error
from escpos.katakana import encode_katakana from escpos.katakana import encode_katakana
from escpos.magicencode import Encoder, MagicEncode from escpos.magicencode import Encoder, MagicEncode
@ -23,23 +23,23 @@ class TestEncoder:
Tests the single encoders. Tests the single encoders.
""" """
def test_can_encode(self): def test_can_encode(self) -> None:
assert not Encoder({"CP437": 1}).can_encode("CP437", "") assert not Encoder({"CP437": 1}).can_encode("CP437", "")
assert Encoder({"CP437": 1}).can_encode("CP437", "á") assert Encoder({"CP437": 1}).can_encode("CP437", "á")
assert not Encoder({"foobar": 1}).can_encode("foobar", "a") assert not Encoder({"foobar": 1}).can_encode("foobar", "a")
def test_find_suitable_encoding(self): def test_find_suitable_encoding(self) -> None:
assert not Encoder({"CP437": 1}).find_suitable_encoding("") assert not Encoder({"CP437": 1}).find_suitable_encoding("")
assert Encoder({"CP858": 1}).find_suitable_encoding("") == "CP858" assert Encoder({"CP858": 1}).find_suitable_encoding("") == "CP858"
def test_find_suitable_encoding_unnecessary_codepage_swap(self): def test_find_suitable_encoding_unnecessary_codepage_swap(self) -> None:
enc = Encoder({"CP857": 1, "CP437": 2, "CP1252": 3, "CP852": 4, "CP858": 5}) enc = Encoder({"CP857": 1, "CP437": 2, "CP1252": 3, "CP852": 4, "CP858": 5})
# desired behavior would be that the encoder always stays in the lower # desired behavior would be that the encoder always stays in the lower
# available codepages if possible # available codepages if possible
for character in ("Á", "É", "Í", "Ó", "Ú"): for character in ("Á", "É", "Í", "Ó", "Ú"):
assert enc.find_suitable_encoding(character) == "CP857" assert enc.find_suitable_encoding(character) == "CP857"
def test_get_encoding(self): def test_get_encoding(self) -> None:
with pytest.raises(ValueError): with pytest.raises(ValueError):
Encoder({}).get_encoding_name("latin1") Encoder({}).get_encoding_name("latin1")
@ -122,9 +122,9 @@ class TestKatakana:
@example("カタカナ") @example("カタカナ")
@example("あいうえお") @example("あいうえお")
@example("ハンカクカタカナ") @example("ハンカクカタカナ")
def test_accept(self, text): def test_accept(self, text: str) -> None:
encode_katakana(text) encode_katakana(text)
def test_result(self): def test_result(self) -> None:
assert encode_katakana("カタカナ") == b"\xb6\xc0\xb6\xc5" assert encode_katakana("カタカナ") == b"\xb6\xc0\xb6\xc5"
assert encode_katakana("あいうえお") == b"\xb1\xb2\xb3\xb4\xb5" assert encode_katakana("あいうえお") == b"\xb1\xb2\xb3\xb4\xb5"

View File

@ -19,7 +19,7 @@ pytestmark = pytest.mark.skipif(
) )
def test_device_not_initialized(cupsprinter): def test_device_not_initialized(cupsprinter) -> None:
""" """
GIVEN a cups printer object GIVEN a cups printer object
WHEN it is not initialized WHEN it is not initialized
@ -28,7 +28,7 @@ def test_device_not_initialized(cupsprinter):
assert cupsprinter._device is False assert cupsprinter._device is False
def test_open_raise_exception(cupsprinter, devicenotfounderror): def test_open_raise_exception(cupsprinter, devicenotfounderror) -> None:
""" """
GIVEN a cups printer object GIVEN a cups printer object
WHEN open() is set to raise a DeviceNotFoundError on error WHEN open() is set to raise a DeviceNotFoundError on error
@ -40,7 +40,7 @@ def test_open_raise_exception(cupsprinter, devicenotfounderror):
cupsprinter.open(raise_not_found=True) cupsprinter.open(raise_not_found=True)
def test_open_not_raise_exception(cupsprinter, caplog): def test_open_not_raise_exception(cupsprinter, caplog) -> None:
""" """
GIVEN a cups printer object GIVEN a cups printer object
WHEN open() is set to not raise on error but simply cancel WHEN open() is set to not raise on error but simply cancel
@ -55,7 +55,7 @@ def test_open_not_raise_exception(cupsprinter, caplog):
assert cupsprinter.device is None assert cupsprinter.device is None
def test_open(cupsprinter, caplog, mocker): def test_open(cupsprinter, caplog, mocker) -> None:
""" """
GIVEN a cups printer object and a mocked pycups device GIVEN a cups printer object and a mocked pycups device
WHEN a valid connection to a device is opened WHEN a valid connection to a device is opened
@ -74,7 +74,7 @@ def test_open(cupsprinter, caplog, mocker):
assert cupsprinter.device assert cupsprinter.device
def test_close_on_reopen(cupsprinter, mocker): def test_close_on_reopen(cupsprinter, mocker) -> None:
""" """
GIVEN a cups printer object and a mocked connection GIVEN a cups printer object and a mocked connection
WHEN a valid connection to a device is reopened before close WHEN a valid connection to a device is reopened before close
@ -112,7 +112,7 @@ def test_close(cupsprinter, caplog, mocker):
assert cupsprinter._device is False assert cupsprinter._device is False
def test_send_on_close(cupsprinter, mocker): def test_send_on_close(cupsprinter, mocker) -> None:
""" """
GIVEN a cups printer object and a mocked pycups device GIVEN a cups printer object and a mocked pycups device
WHEN closing connection before send the buffer WHEN closing connection before send the buffer
@ -133,7 +133,7 @@ def test_send_on_close(cupsprinter, mocker):
assert cupsprinter.pending_job is False assert cupsprinter.pending_job is False
def test_raw_raise_exception(cupsprinter): def test_raw_raise_exception(cupsprinter) -> None:
""" """
GIVEN a cups printer object GIVEN a cups printer object
WHEN passing a non byte string to _raw() WHEN passing a non byte string to _raw()
@ -145,7 +145,7 @@ def test_raw_raise_exception(cupsprinter):
assert cupsprinter.pending_job is False assert cupsprinter.pending_job is False
def test_raw(cupsprinter): def test_raw(cupsprinter) -> None:
""" """
GIVEN a cups printer object GIVEN a cups printer object
WHEN passing a byte string to _raw() WHEN passing a byte string to _raw()
@ -156,7 +156,7 @@ def test_raw(cupsprinter):
assert cupsprinter.tmpfile.read() == b"Test" assert cupsprinter.tmpfile.read() == b"Test"
def test_printers_no_device(cupsprinter): def test_printers_no_device(cupsprinter) -> None:
""" """
GIVEN a cups printer object GIVEN a cups printer object
WHEN device is None WHEN device is None
@ -166,11 +166,11 @@ def test_printers_no_device(cupsprinter):
assert cupsprinter.printers == {} assert cupsprinter.printers == {}
def test_read_no_device(cupsprinter): def test_read_no_device(cupsprinter) -> None:
""" """
GIVEN a cups printer object GIVEN a cups printer object
WHEN device is None WHEN device is None
THEN check the return value is [] THEN check the return value is b'8'
""" """
cupsprinter.device = None cupsprinter.device = None
assert cupsprinter._read() == [] assert cupsprinter._read() == b"8"

View File

@ -58,6 +58,7 @@ deps = mypy
types-Pillow types-Pillow
types-pyserial types-pyserial
types-pywin32>=306.0.0.6 types-pywin32>=306.0.0.6
types-flask
hypothesis>=6.83 hypothesis>=6.83
jaconv jaconv
commands = mypy src test commands = mypy src test examples