1
0
mirror of https://github.com/python-escpos/python-escpos synced 2025-09-13 09:09:58 +00:00

17 Commits
v3.0 ... v3.1

Author SHA1 Message Date
Patrick Kanzler
0d22896689 Prepare release 3.1 (#614)
* update authors
* update changelog
* update spelling
* update black
2023-12-17 22:53:19 +01:00
Patrick Kanzler
b66dafc90e improve type annotations in tests (#613) 2023-12-17 01:15:59 +01:00
Patrick Kanzler
0c824cf295 More mypy (#612)
* remove type comment where type is annotated
* move function tests
* remove six from tests
* add none annotations
* add more types
* change mock (so that mypy understands it)
2023-12-16 23:09:20 +01:00
Alexandre Detiste
66a2e78e16 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>
2023-12-16 22:02:24 +01:00
dependabot[bot]
06bdb56937 Bump github/codeql-action from 2 to 3 (#610)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 2 to 3.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-15 11:53:52 +01:00
Patrick Kanzler
91cbc264fc Refactor to using f-strings (#608)
* update authors

* refactor to using f-strings
2023-12-11 00:34:29 +01:00
tuxmaster
dcc71ce47d argparse is an part of the python core (#606) 2023-12-09 21:26:19 +01:00
dependabot[bot]
d2b213dcdc Bump actions/setup-python from 4.8.0 to 5.0.0 (#605)
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4.8.0 to 5.0.0.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v4.8.0...v5.0.0)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-07 23:52:37 +01:00
dependabot[bot]
c91eec544e Bump actions/setup-python from 4.7.1 to 4.8.0 (#604)
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4.7.1 to 4.8.0.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v4.7.1...v4.8.0)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-06 00:45:25 +01:00
Patrick Kanzler
a9e49c909d Update CHANGELOG.rst (#603) 2023-12-06 00:31:12 +01:00
Patrick Kanzler
815f247df1 Improve test coverage and fix issue in path loading (#602)
* reactivate skipped tests

* remove unused internal interfaces

* enable pytest in VS Code

* simplify and fix path logic in loader

* increase test coverage

* test missing config

* check for wrong printer names

* Skipped appdir test
2023-12-06 00:25:36 +01:00
Patrick Kanzler
5914c7c560 allow qr to set all arguments to image (#600)
* allow qr to set all arguments to image

* increase coverage
2023-12-04 01:15:19 +01:00
Patrick Kanzler
86a715cf02 update capabilities data (#601) 2023-12-04 01:11:05 +01:00
Patrick Kanzler
ac23c083b6 update to newest python-barcode (#595)
* update to newest python-barcode
2023-12-03 23:57:34 +01:00
Patrick Kanzler
9a1699ab94 add type annotations for escpos image handler (#599) 2023-12-03 23:36:35 +01:00
dependabot[bot]
8274833255 Bump sphinx-rtd-theme from 1.3.0 to 2.0.0 (#596)
Bumps [sphinx-rtd-theme](https://github.com/readthedocs/sphinx_rtd_theme) from 1.3.0 to 2.0.0.
- [Changelog](https://github.com/readthedocs/sphinx_rtd_theme/blob/master/docs/changelog.rst)
- [Commits](https://github.com/readthedocs/sphinx_rtd_theme/compare/1.3.0...2.0.0)

---
updated-dependencies:
- dependency-name: sphinx-rtd-theme
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-30 08:38:40 +01:00
Patrick Kanzler
33d17615a0 prepare next release development cycle (#594) 2023-11-17 01:12:31 +01:00
60 changed files with 643 additions and 448 deletions

View File

@@ -9,5 +9,5 @@ jobs:
- uses: actions/checkout@v4
- uses: psf/black@stable
with:
version: "23.3.0"
version: "23.12.0"

View File

@@ -38,7 +38,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -49,7 +49,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
uses: github/codeql-action/autobuild@v3
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@@ -63,4 +63,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
uses: github/codeql-action/analyze@v3

View File

@@ -19,7 +19,7 @@ jobs:
with:
submodules: 'recursive'
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4.7.1
uses: actions/setup-python@v5.0.0
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies

View File

@@ -22,7 +22,7 @@ jobs:
with:
submodules: 'recursive'
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4.7.1
uses: actions/setup-python@v5.0.0
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies

View File

@@ -14,4 +14,6 @@
"editor.codeActionsOnSave": {
"source.organizeImports": true
},
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true,
}

View File

@@ -2,6 +2,7 @@ Ahmed Tahri
akeonly
Alejandro Hernández
Alexander Bougakov
Alexandre Detiste
Alex Debiasio
Alfredo Orozco
Asuki Kono
@@ -46,6 +47,7 @@ Sergio Pulgarin
Stephan Sokolow
Thijs Triemstra
Thomas van den Berg
tuxmaster
vendryan
Yaisel Hurtado
ysuolmai

View File

@@ -1,6 +1,30 @@
Changelog
=========
2023-12-17 - Version 3.1 - "Rubric Of Ruin"
-------------------------------------------
This is the minor release of the new version 3.1.
It adds a modification of the API of the qr-method,
hence the minor release.
changes
^^^^^^^
- extend API of the qr-method to allow passing image
parameters in non-native mode
- use version 0.15 and upwards of python-barcode
- fix an issue in the config provider that prevented
config files to be found when only a path was supplied
- Improve type annotations, usage of six and other
packaging relevant parts.
contributors
^^^^^^^^^^^^
- Patrick Kanzler
- Alexandre Detiste
- tuxmaster
- belono
2023-11-17 - Version 3.0 - "Quietly Confident"
----------------------------------------------
This is the major release of the new version 3.0.

View File

@@ -2,12 +2,12 @@ pyusb
Pillow>=2.0
qrcode>=4.0
pyserial
sphinx-rtd-theme==1.3.0
sphinx-rtd-theme==2.0.0
setuptools
setuptools-scm
docutils>=0.12
sphinxcontrib-spelling>=7.2.0
python-barcode>=0.11.0,<1
python-barcode>=0.15.0,<1
importlib-metadata
importlib_resources
sphinxcontrib.datatemplates

View File

@@ -68,6 +68,8 @@ Qian
Lehtonen
Kanzler
Rotondo
Alexandre
Detiste
barcode
barcodes

View File

@@ -3,8 +3,6 @@
import sys
import six
from escpos import printer
from escpos.constants import (
CODEPAGE_CHANGE,
@@ -38,7 +36,7 @@ def print_codepage(printer, codepage):
"""Print a codepage."""
if codepage.isdigit():
codepage = int(codepage)
printer._raw(CODEPAGE_CHANGE + six.int2byte(codepage))
printer._raw(CODEPAGE_CHANGE + bytes((codepage,)))
printer._raw("after")
else:
printer.charcode(codepage)
@@ -47,18 +45,20 @@ def print_codepage(printer, codepage):
# Table header
printer.set(font="b")
printer._raw(" {}\n".format(sep.join(map(lambda s: hex(s)[2:], range(0, 16)))))
printer._raw(f" {sep.join(map(lambda s: hex(s)[2:], range(0, 16)))}\n")
printer.set()
# The table
for x in range(0, 16):
# First column
printer.set(font="b")
printer._raw("{} ".format(hex(x)[2:]))
printer._raw(f"{hex(x)[2:]} ")
printer.set()
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):
byte = " "

View File

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

View File

@@ -3,6 +3,7 @@ name = python-escpos
url = https://github.com/python-escpos/python-escpos
description = Python library to manipulate ESC/POS Printers
long_description = file: README.rst
long_description_content_type = text/x-rst
license = MIT
license_file = LICENSE
author = python-escpos developers
@@ -38,12 +39,11 @@ include_package_data = true
install_requires =
Pillow>=2.0
qrcode>=4.0
python-barcode>=0.9.1,<1
python-barcode>=0.15.0,<1
setuptools
six
appdirs
PyYAML
argparse
argcomplete
importlib_resources
setup_requires = setuptools_scm

View File

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

View File

@@ -11,16 +11,13 @@ from tempfile import mkdtemp
from typing import Any, Dict, Optional, Type
import importlib_resources
import six
import yaml
logging.basicConfig()
logger = logging.getLogger(__name__)
pickle_dir = environ.get("ESCPOS_CAPABILITIES_PICKLE_DIR", mkdtemp())
pickle_path = path.join(
pickle_dir, "{v}.capabilities.pickle".format(v=platform.python_version())
)
pickle_path = path.join(pickle_dir, f"{platform.python_version()}.capabilities.pickle")
# get a temporary file from importlib_resources if no file is specified in env
file_manager = ExitStack()
atexit.register(file_manager.close)
@@ -94,7 +91,7 @@ class NotSupported(Exception):
BARCODE_B = "barcodeB"
class BaseProfile(object):
class BaseProfile:
"""This represents a printer profile.
A printer profile knows about the number of columns, supported
@@ -113,22 +110,22 @@ class BaseProfile(object):
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(
'"{}" is not a valid font in the current profile'.format(font)
)
if not str(font) in self.fonts:
raise NotSupported(f'"{font}" is not a valid font in the current profile')
return font
def get_columns(self, font):
def get_columns(self, font) -> int:
"""Return the number of columns for the given 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 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 {v: k for k, v in self.codePages.items()}
@@ -158,14 +155,14 @@ def get_profile_class(name: str) -> Type[BaseProfile]:
profiles: Dict[str, Any] = CAPABILITIES["profiles"]
profile_data = profiles[name]
profile_name = clean(name)
class_name = "{}{}Profile".format(profile_name[0].upper(), profile_name[1:])
class_name = f"{profile_name[0].upper()}{profile_name[1:]}Profile"
new_class = type(class_name, (BaseProfile,), {"profile_data": profile_data})
CLASS_CACHE[name] = new_class
return CLASS_CACHE[name]
def clean(s):
def clean(s: str) -> str:
"""Clean profile name."""
# Remove invalid characters
s = re.sub("[^0-9a-zA-Z_]", "", s)
@@ -184,14 +181,14 @@ class Profile(ProfileBaseClass):
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."""
super(Profile, self).__init__()
self.columns = columns
self.features = features or {}
def get_columns(self, font):
def get_columns(self, font) -> int:
"""Get column count of printer."""
if self.columns is not None:
return self.columns

View File

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

View File

@@ -12,10 +12,6 @@ class CodePageManager:
"""Initialize codepage manager."""
self.data = data
def get_all(self):
"""Get list of all codepages."""
return self.data.values()
@staticmethod
def get_encoding_name(encoding):
"""Get encoding name.

View File

@@ -3,6 +3,7 @@
This module contains the implementations of abstract base class :py:class:`Config`.
"""
import os
import pathlib
import appdirs
import yaml
@@ -10,7 +11,7 @@ import yaml
from . import exceptions, printer
class Config(object):
class Config:
"""Configuration handler class.
This class loads configuration from a default or specified directory. It
@@ -20,7 +21,7 @@ class Config(object):
_app_name = "python-escpos"
_config_file = "config.yaml"
def __init__(self):
def __init__(self) -> None:
"""Initialize configuration.
Remember to add anything that needs to be reset between configurations
@@ -32,7 +33,7 @@ class Config(object):
self._printer_name = None
self._printer_config = None
def _reset_config(self):
def _reset_config(self) -> None:
"""Clear the loaded configuration.
If we are loading a changed config, we don't want to have leftover
@@ -56,23 +57,19 @@ class Config(object):
config_path = os.path.join(
appdirs.user_config_dir(self._app_name), self._config_file
)
if isinstance(config_path, pathlib.Path):
# store string if posixpath
config_path = config_path.as_posix()
if not os.path.isfile(config_path):
# supplied path is not a file --> assume default file
config_path = os.path.join(config_path, self._config_file)
try:
# First check if it's file like. If it is, pyyaml can load it.
# I'm checking type instead of catching exceptions to keep the
# exception handling simple
if hasattr(config_path, "read"):
config = yaml.safe_load(config_path)
else:
# If it isn't, it's a path. We have to open it first, otherwise
# pyyaml will try to read it as yaml
with open(config_path, "rb") as config_file:
config = yaml.safe_load(config_file)
with open(config_path, "rb") as config_file:
config = yaml.safe_load(config_file)
except EnvironmentError:
raise exceptions.ConfigNotFoundError(
"Couldn't read config at {config_path}".format(
config_path=str(config_path),
)
f"Couldn't read config at {config_path}"
)
except yaml.YAMLError:
raise exceptions.ConfigSyntaxError("Error parsing YAML")
@@ -83,9 +80,7 @@ class Config(object):
if not self._printer_name or not hasattr(printer, self._printer_name):
raise exceptions.ConfigSyntaxError(
'Printer type "{printer_name}" is invalid'.format(
printer_name=self._printer_name,
)
f'Printer type "{self._printer_name}" is invalid'
)
self._has_loaded = True

View File

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

View File

@@ -9,12 +9,14 @@ This module contains the abstract base class :py:class:`Escpos`.
:copyright: Copyright (c) 2012-2017 Bashlinux and python-escpos
:license: MIT
"""
from __future__ import annotations
import textwrap
import warnings
from abc import ABCMeta, abstractmethod # abstract base class support
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 qrcode
@@ -106,8 +108,7 @@ SW_BARCODE_NAMES = {
}
@six.add_metaclass(ABCMeta)
class Escpos(object):
class Escpos(object, metaclass=ABCMeta):
"""ESC/POS Printer object.
This class is the abstract base class for an Esc/Pos-printer. The printer implementations are children of this
@@ -153,6 +154,10 @@ class Escpos(object):
"""Open a printer device/connection."""
pass
def close(self):
"""Close a printer device/connection."""
pass
@abstractmethod
def _raw(self, msg: bytes) -> None:
"""Send raw data to the printer.
@@ -163,7 +168,7 @@ class Escpos(object):
"""
pass
def _read(self):
def _read(self) -> bytes:
"""Read from printer.
Returns a NotImplementedError if the instance of the class doesn't override this method.
@@ -174,11 +179,11 @@ class Escpos(object):
def image(
self,
img_source,
high_density_vertical=True,
high_density_horizontal=True,
impl="bitImageRaster",
fragment_height=960,
center=False,
high_density_vertical: bool = True,
high_density_horizontal: bool = True,
impl: str = "bitImageRaster",
fragment_height: int = 960,
center: bool = False,
) -> None:
"""Print an image.
@@ -218,7 +223,7 @@ class Escpos(object):
max_width = int(self.profile.profile_data["media"]["width"]["pixels"])
if im.width > max_width:
raise ImageWidthError("{} > {}".format(im.width, max_width))
raise ImageWidthError(f"{im.width} > {max_width}")
if center:
im.center(max_width)
@@ -249,7 +254,7 @@ class Escpos(object):
header = (
GS
+ b"v0"
+ six.int2byte(density_byte)
+ bytes((density_byte,))
+ self._int_low_high(im.width_bytes, 2)
+ self._int_low_high(im.height, 2)
)
@@ -262,8 +267,8 @@ class Escpos(object):
)
tone = b"0"
colors = b"1"
ym = six.int2byte(1 if high_density_vertical else 2)
xm = six.int2byte(1 if high_density_horizontal else 2)
ym = b"\x01" if high_density_vertical else b"\x02"
xm = b"\x01" if high_density_horizontal else b"\x02"
header = tone + xm + ym + colors + img_header
raster_data = im.to_raster_format()
self._image_send_graphics_data(b"0", b"p", header + raster_data)
@@ -304,7 +309,8 @@ class Escpos(object):
model=QR_MODEL_2,
native=False,
center=False,
impl="bitImageRaster",
impl=None,
image_arguments: Optional[dict] = None,
) -> None:
"""Print QR Code for the provided string.
@@ -319,6 +325,8 @@ class Escpos(object):
printer (Default)
:param center: Centers the code *default:* False
:param impl: Image-printing-implementation, refer to :meth:`.image()` for details
:param image_arguments: arguments passed to :meth:`.image()`.
Replaces `impl` and `center`. If `impl` or `center` are set, they will overwrite `image_arguments`.
"""
# Basic validation
if ec not in [QR_ECLEVEL_L, QR_ECLEVEL_M, QR_ECLEVEL_H, QR_ECLEVEL_Q]:
@@ -333,6 +341,19 @@ class Escpos(object):
# Handle edge case by printing nothing.
return
if not native:
# impl is deprecated in favor of image_arguments
if impl:
warnings.warn(
"Parameter impl is deprecated in favor of image_arguments and will be dropped in a future release.",
DeprecationWarning,
)
# assemble arguments for image
if not image_arguments:
image_arguments = {}
if impl:
image_arguments["impl"] = impl
if "center" not in image_arguments:
image_arguments["center"] = center
# Map ESC/POS error correction levels to python 'qrcode' library constant and render to an image
if model != QR_MODEL_2:
raise ValueError(
@@ -354,7 +375,7 @@ class Escpos(object):
# Convert the RGB image in printable image
self.text("\n")
self.image(im, center=center, impl=impl)
self.image(im, **image_arguments)
self.text("\n")
self.text("\n")
return
@@ -403,9 +424,7 @@ class Escpos(object):
raise ValueError("Can only output 1-4 bytes")
if not 0 <= inp_number <= max_input:
raise ValueError(
"Number too large. Can only output up to {0} in {1} bytes".format(
max_input, out_bytes
)
f"Number too large. Can only output up to {max_input} in {out_bytes} bytes"
)
outp = b""
for _ in range(0, out_bytes):
@@ -694,21 +713,15 @@ class Escpos(object):
if not function_type or not BARCODE_TYPES.get(function_type.upper()):
raise BarcodeTypeError(
(
"Barcode '{bc}' not valid for barcode function type "
"{function_type}"
).format(
bc=bc,
function_type=function_type,
f"Barcode '{bc}' not valid for barcode function type "
f"{function_type}"
)
)
bc_types = BARCODE_TYPES[function_type.upper()]
if check and not self.check_barcode(bc, code):
raise BarcodeCodeError(
("Barcode '{code}' not in a valid format for type '{bc}'").format(
code=code,
bc=bc,
)
f"Barcode '{code}' not in a valid format for type '{bc}'"
)
# Align Bar Code()
@@ -718,12 +731,12 @@ class Escpos(object):
if 1 <= height <= 255:
self._raw(BARCODE_HEIGHT + six.int2byte(height))
else:
raise BarcodeSizeError("height = {height}".format(height=height))
raise BarcodeSizeError(f"height = {height}")
# Width
if 2 <= width <= 6:
self._raw(BARCODE_WIDTH + six.int2byte(width))
else:
raise BarcodeSizeError("width = {width}".format(width=width))
raise BarcodeSizeError(f"width = {width}")
# Font
if font.upper() == "B":
self._raw(BARCODE_FONT_B)
@@ -816,9 +829,7 @@ class Escpos(object):
# Check if barcode type exists
if barcode_type not in barcode.PROVIDED_BARCODES:
raise BarcodeTypeError(
"Barcode type {} not supported by software barcode renderer".format(
barcode_type
)
f"Barcode type {barcode_type} not supported by software barcode renderer"
)
# Render the barcode
@@ -840,7 +851,7 @@ class Escpos(object):
image = my_code.writer._image
self.image(image, impl=impl, center=center)
def text(self, txt):
def text(self, txt: str) -> None:
"""Print alpha-numeric text.
The text has to be encoded in the currently selected codepage.
@@ -849,10 +860,9 @@ class Escpos(object):
:param txt: text to be printed
:raises: :py:exc:`~escpos.exceptions.TextError`
"""
txt = six.text_type(txt)
self.magic.write(txt)
self.magic.write(str(txt))
def textln(self, txt=""):
def textln(self, txt: str = "") -> None:
"""Print alpha-numeric text with a newline.
The text has to be encoded in the currently selected codepage.
@@ -861,9 +871,9 @@ class Escpos(object):
:param txt: text to be printed with a newline
:raises: :py:exc:`~escpos.exceptions.TextError`
"""
self.text("{}\n".format(txt))
self.text(f"{txt}\n")
def ln(self, count=1):
def ln(self, count: int = 1) -> None:
"""Print a newline or more.
:param count: number of newlines to print
@@ -874,7 +884,7 @@ class Escpos(object):
if count > 0:
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.
Text has to be encoded in unicode.
@@ -1125,7 +1135,7 @@ class Escpos(object):
try:
self._raw(CD_KICK_DEC_SEQUENCE(*pin))
except TypeError as err:
raise CashDrawerError(err)
raise CashDrawerError(str(err))
def linedisplay_select(self, select_display: bool = False) -> None:
"""Select the line display or the printer.
@@ -1258,10 +1268,10 @@ class Escpos(object):
else:
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.
Returns an array of integers containing it.
Returns byte array containing it.
:param mode: Integer that sets the status mode queried to the printer.
- RT_STATUS_ONLINE: Printer status.
@@ -1281,7 +1291,7 @@ class Escpos(object):
return False
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.
Returns 2 if there is plenty of paper, 1 if the paper has arrived to
@@ -1298,6 +1308,8 @@ class Escpos(object):
return 1
if status[0] & RT_MASK_PAPER == RT_MASK_PAPER:
return 2
# not reached
return 0
def target(self, type: str = "ROLL") -> None:
"""Select where to print to.
@@ -1352,7 +1364,7 @@ class Escpos(object):
self._raw(BUZZER + six.int2byte(times) + six.int2byte(duration))
class EscposIO(object):
class EscposIO:
r"""ESC/POS Printer IO object.
Allows the class to be used together with the `with`-statement. You have to define a printer instance
@@ -1398,41 +1410,40 @@ class EscposIO(object):
"""
self.params.update(kwargs)
def writelines(self, text, **kwargs):
def writelines(self, text: str, **kwargs) -> None:
"""Print text."""
params = dict(self.params)
params.update(kwargs)
if isinstance(text, six.text_type):
if isinstance(text, str):
lines = text.split("\n")
elif isinstance(text, list) or isinstance(text, tuple):
lines = text
else:
lines = [
"{0}".format(text),
f"{text}",
]
# TODO check unicode handling
# TODO flush? or on print? (this should prob rather be handled by the _raw-method)
for line in lines:
self.printer.set(**params)
if isinstance(text, six.text_type):
self.printer.text("{0}\n".format(line))
else:
self.printer.text("{0}\n".format(line))
self.printer.text(f"{line}\n")
def close(self):
def close(self) -> None:
"""Close printer.
Called upon closing the `with`-statement.
"""
self.printer.close()
def __enter__(self, **kwargs):
def __enter__(self, **kwargs: Any) -> "EscposIO":
"""Enter context."""
return self
def __exit__(self, type, value, traceback):
def __exit__(
self, type: type[BaseException], value: BaseException, traceback: TracebackType
) -> None:
"""Cut and close if configured.
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
"""
from typing import Optional
class Error(Exception):
"""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."""
Exception.__init__(self)
self.msg = msg
@@ -45,7 +47,7 @@ class Error(Exception):
if status is not None:
self.resultcode = status
def __str__(self):
def __str__(self) -> str:
"""Return string representation of Error."""
return self.msg
@@ -64,15 +66,15 @@ class BarcodeTypeError(Error):
"""
def __init__(self, msg=""):
def __init__(self, msg: str = "") -> None:
"""Initialize BarcodeTypeError object."""
Error.__init__(self, msg)
self.msg = msg
self.resultcode = 10
def __str__(self):
def __str__(self) -> str:
"""Return string representation of BarcodeTypeError."""
return "No Barcode type is defined ({msg})".format(msg=self.msg)
return f"No Barcode type is defined ({self.msg})"
class BarcodeSizeError(Error):
@@ -89,15 +91,15 @@ class BarcodeSizeError(Error):
"""
def __init__(self, msg=""):
def __init__(self, msg: str = "") -> None:
"""Initialize BarcodeSizeError object."""
Error.__init__(self, msg)
self.msg = msg
self.resultcode = 20
def __str__(self):
def __str__(self) -> str:
"""Return string representation of BarcodeSizeError."""
return "Barcode size is out of range ({msg})".format(msg=self.msg)
return f"Barcode size is out of range ({self.msg})"
class BarcodeCodeError(Error):
@@ -114,15 +116,15 @@ class BarcodeCodeError(Error):
"""
def __init__(self, msg=""):
def __init__(self, msg: str = "") -> None:
"""Initialize BarcodeCodeError object."""
Error.__init__(self, msg)
self.msg = msg
self.resultcode = 30
def __str__(self):
def __str__(self) -> str:
"""Return string representation of BarcodeCodeError."""
return "No Barcode code was supplied ({msg})".format(msg=self.msg)
return f"No Barcode code was supplied ({self.msg})"
class ImageSizeError(Error):
@@ -137,17 +139,15 @@ class ImageSizeError(Error):
"""
def __init__(self, msg=""):
def __init__(self, msg: str = "") -> None:
"""Initialize ImageSizeError object."""
Error.__init__(self, msg)
self.msg = msg
self.resultcode = 40
def __str__(self):
def __str__(self) -> str:
"""Return string representation of ImageSizeError."""
return "Image height is longer than 255px and can't be printed ({msg})".format(
msg=self.msg
)
return f"Image height is longer than 255px and can't be printed ({self.msg})"
class ImageWidthError(Error):
@@ -162,15 +162,15 @@ class ImageWidthError(Error):
"""
def __init__(self, msg=""):
def __init__(self, msg: str = "") -> None:
"""Initialize ImageWidthError object."""
Error.__init__(self, msg)
self.msg = msg
self.resultcode = 41
def __str__(self):
def __str__(self) -> str:
"""Return string representation of ImageWidthError."""
return "Image width is too large ({msg})".format(msg=self.msg)
return f"Image width is too large ({self.msg})"
class TextError(Error):
@@ -186,17 +186,15 @@ class TextError(Error):
"""
def __init__(self, msg=""):
def __init__(self, msg: str = "") -> None:
"""Initialize TextError object."""
Error.__init__(self, msg)
self.msg = msg
self.resultcode = 50
def __str__(self):
def __str__(self) -> str:
"""Return string representation of TextError."""
return "Text string must be supplied to the text() method ({msg})".format(
msg=self.msg
)
return f"Text string must be supplied to the text() method ({self.msg})"
class CashDrawerError(Error):
@@ -212,15 +210,15 @@ class CashDrawerError(Error):
"""
def __init__(self, msg=""):
def __init__(self, msg: str = "") -> None:
"""Initialize CashDrawerError object."""
Error.__init__(self, msg)
self.msg = msg
self.resultcode = 60
def __str__(self):
def __str__(self) -> str:
"""Return string representation of CashDrawerError."""
return "Valid pin must be set to send pulse ({msg})".format(msg=self.msg)
return f"Valid pin must be set to send pulse ({self.msg})"
class TabPosError(Error):
@@ -239,17 +237,15 @@ class TabPosError(Error):
"""
def __init__(self, msg=""):
def __init__(self, msg: str = "") -> None:
"""Initialize TabPosError object."""
Error.__init__(self, msg)
self.msg = msg
self.resultcode = 70
def __str__(self):
def __str__(self) -> str:
"""Return string representation of TabPosError."""
return "Valid tab positions must be in the range 0 to 16 ({msg})".format(
msg=self.msg
)
return f"Valid tab positions must be in the range 0 to 16 ({self.msg})"
class CharCodeError(Error):
@@ -265,15 +261,15 @@ class CharCodeError(Error):
"""
def __init__(self, msg=""):
def __init__(self, msg: str = "") -> None:
"""Initialize CharCodeError object."""
Error.__init__(self, msg)
self.msg = msg
self.resultcode = 80
def __str__(self):
def __str__(self) -> str:
"""Return string representation of CharCodeError."""
return "Valid char code must be set ({msg})".format(msg=self.msg)
return f"Valid char code must be set ({self.msg})"
class DeviceNotFoundError(Error):
@@ -289,13 +285,13 @@ class DeviceNotFoundError(Error):
"""
def __init__(self, msg=""):
def __init__(self, msg: str = "") -> None:
"""Initialize DeviceNotFoundError object."""
Error.__init__(self, msg)
self.msg = msg
self.resultcode = 90
def __str__(self):
def __str__(self) -> str:
"""Return string representation of DeviceNotFoundError."""
return f"Device not found ({self.msg})"
@@ -313,13 +309,13 @@ class USBNotFoundError(DeviceNotFoundError):
"""
def __init__(self, msg=""):
def __init__(self, msg: str = "") -> None:
"""Initialize USBNotFoundError object."""
Error.__init__(self, msg)
self.msg = msg
self.resultcode = 91
def __str__(self):
def __str__(self) -> str:
"""Return string representation of USBNotFoundError."""
return f"USB device not found ({self.msg})"
@@ -337,15 +333,15 @@ class SetVariableError(Error):
"""
def __init__(self, msg=""):
def __init__(self, msg: str = "") -> None:
"""Initialize SetVariableError object."""
Error.__init__(self, msg)
self.msg = msg
self.resultcode = 100
def __str__(self):
def __str__(self) -> str:
"""Return string representation of SetVariableError."""
return "Set variable out of range ({msg})".format(msg=self.msg)
return f"Set variable out of range ({self.msg})"
# Configuration errors
@@ -364,15 +360,15 @@ class ConfigNotFoundError(Error):
"""
def __init__(self, msg=""):
def __init__(self, msg: str = "") -> None:
"""Initialize ConfigNotFoundError object."""
Error.__init__(self, msg)
self.msg = msg
self.resultcode = 200
def __str__(self):
def __str__(self) -> str:
"""Return string representation of ConfigNotFoundError."""
return "Configuration not found ({msg})".format(msg=self.msg)
return f"Configuration not found ({self.msg})"
class ConfigSyntaxError(Error):
@@ -388,15 +384,15 @@ class ConfigSyntaxError(Error):
"""
def __init__(self, msg=""):
def __init__(self, msg: str = "") -> None:
"""Initialize ConfigSyntaxError object."""
Error.__init__(self, msg)
self.msg = msg
self.resultcode = 210
def __str__(self):
def __str__(self) -> str:
"""Return string representation of ConfigSyntaxError."""
return "Configuration syntax is invalid ({msg})".format(msg=self.msg)
return f"Configuration syntax is invalid ({self.msg})"
class ConfigSectionMissingError(Error):
@@ -412,12 +408,12 @@ class ConfigSectionMissingError(Error):
"""
def __init__(self, msg=""):
def __init__(self, msg: str = "") -> None:
"""Initialize ConfigSectionMissingError object."""
Error.__init__(self, msg)
self.msg = msg
self.resultcode = 220
def __str__(self):
def __str__(self) -> str:
"""Return string representation of ConfigSectionMissingError."""
return "Configuration section is missing ({msg})".format(msg=self.msg)
return f"Configuration section is missing ({self.msg})"

View File

@@ -10,11 +10,12 @@ This module contains the image format handler :py:class:`EscposImage`.
import math
from typing import Iterator, Union
from PIL import Image, ImageOps
class EscposImage(object):
class EscposImage:
"""
Load images in, and output ESC/POS formats.
@@ -22,7 +23,7 @@ class EscposImage(object):
PIL, rather than spend CPU cycles looping over pixels.
"""
def __init__(self, img_source):
def __init__(self, img_source: Union[Image.Image, str]) -> None:
"""Load in an image.
:param img_source: PIL.Image, or filename to load one from.
@@ -48,23 +49,23 @@ class EscposImage(object):
self._im = im.convert("1")
@property
def width(self):
def width(self) -> int:
"""Return width of image in pixels."""
width_pixels, _ = self._im.size
return width_pixels
@property
def width_bytes(self):
def width_bytes(self) -> int:
"""Return width of image if you use 8 pixels per byte and 0-pad at the end."""
return (self.width + 7) >> 3
@property
def height(self):
def height(self) -> int:
"""Height of image in pixels."""
_, height_pixels = self._im.size
return height_pixels
def to_column_format(self, high_density_vertical=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.
:param high_density_vertical: Printed line height in dots
@@ -81,11 +82,11 @@ class EscposImage(object):
yield (im_bytes)
left += line_height
def to_raster_format(self):
def to_raster_format(self) -> bytes:
"""Convert image to raster-format binary."""
return self._im.tobytes()
def split(self, fragment_height):
def split(self, fragment_height: int):
"""Split an image into multiple fragments after fragment_height pixels.
:param fragment_height: height of fragment
@@ -102,7 +103,7 @@ class EscposImage(object):
fragments.append(self.img_original.crop(box))
return fragments
def center(self, max_width):
def center(self, max_width: int) -> None:
"""Center image in place.
:param: Maximum width in order to deduce x offset for centering

View File

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

View File

@@ -23,7 +23,7 @@ from .constants import CODEPAGE_CHANGE
from .exceptions import Error
class Encoder(object):
class Encoder:
"""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
@@ -57,9 +57,9 @@ class Encoder(object):
if encoding not in self.codepages:
raise ValueError(
(
'Encoding "{}" cannot be used for the current profile. '
"Valid encodings are: {}"
).format(encoding, ",".join(self.codepages.keys()))
f'Encoding "{encoding}" cannot be used for the current profile. '
f'Valid encodings are: {",".join(self.codepages.keys())}'
)
)
return encoding
@@ -88,7 +88,7 @@ class Encoder(object):
# Non-encodable character, just skip it
pass
return encodable_chars
raise LookupError("Can't find a known encoding for {}".format(encoding))
raise LookupError(f"Can't find a known encoding for {encoding}")
def _get_codepage_char_map(self, encoding):
"""Get codepage character map.
@@ -202,7 +202,7 @@ def split_writable_text(encoder, text, encoding):
return text, None
class MagicEncode(object):
class MagicEncode:
"""Help switching to the right code page.
A helper that helps us to automatically switch to the right
@@ -292,11 +292,9 @@ class MagicEncode(object):
def write_with_encoding(self, encoding, text):
"""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(
"The supplied text has to be unicode, but is of type {type}.".format(
type=type(text)
)
f"The supplied text has to be unicode, but is of type {type(text)}."
)
# We always know the current code page; if the new codepage

View File

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

View File

@@ -7,6 +7,7 @@
:copyright: Copyright (c) 2012-2023 Bashlinux and python-escpos
:license: MIT
"""
from typing import List
from ..escpos import Escpos
@@ -39,25 +40,24 @@ class Dummy(Escpos):
"""
return is_usable()
def __init__(self, *args, **kwargs):
def __init__(self, *args, **kwargs) -> None:
"""Init with empty output list."""
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.
:param msg: arbitrary code to be printed
:type msg: bytes
"""
self._output_list.append(msg)
@property
def output(self):
def output(self) -> bytes:
"""Get the data that was sent to this printer."""
return b"".join(self._output_list)
def clear(self):
def clear(self) -> None:
"""Clear the buffer of the 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[:]
def close(self):
def close(self) -> None:
"""Close not implemented for Dummy printer."""
pass

View File

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

View File

@@ -182,12 +182,13 @@ class LP(Escpos):
if not self._is_closing:
self.open(_close_opened=False)
def _raw(self, msg):
def _raw(self, msg: bytes) -> None:
"""Write raw command(s) to the printer.
: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():
self.device.stdin.write(msg)
else:

View File

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

View File

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

View File

@@ -74,9 +74,9 @@ class Usb(Escpos):
def __init__(
self,
idVendor: str = "",
idProduct: str = "",
usb_args: Dict[str, str] = {},
idVendor: Optional[int] = None,
idProduct: Optional[int] = None,
usb_args: Dict[str, Union[str, int]] = {},
timeout: Union[int, float] = 0,
in_ep: int = 0x82,
out_ep: int = 0x01,
@@ -181,16 +181,17 @@ class Usb(Escpos):
except usb.core.USBError as 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.
:param msg: arbitrary code to be printed
:type msg: bytes
"""
assert self.device
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."""
assert self.device
return self.device.read(self.in_ep, 16)
@dependency_usb

View File

@@ -76,7 +76,7 @@ class Win32Raw(Escpos):
return is_usable()
@dependency_win32print
def __init__(self, printer_name: str = "", *args, **kwargs):
def __init__(self, printer_name: str = "", *args, **kwargs) -> None:
"""Initialize default printer."""
Escpos.__init__(self, *args, **kwargs)
self.printer_name = printer_name
@@ -148,14 +148,17 @@ class Win32Raw(Escpos):
win32print.ClosePrinter(self._device)
self._device = False
def _raw(self, msg) -> None:
def _raw(self, msg: bytes) -> None:
"""Print any command sent in raw format.
:param msg: arbitrary code to be printed
:type msg: bytes
"""
if self.printer_name is None:
raise DeviceNotFoundError("Printer not found")
if not self.device:
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

@@ -5,32 +5,32 @@ from escpos.printer import LP, CupsPrinter, Dummy, File, Network, Serial, Usb, W
@pytest.fixture
def driver():
def driver() -> Dummy:
return Dummy()
@pytest.fixture
def usbprinter():
def usbprinter() -> Usb:
return Usb()
@pytest.fixture
def serialprinter():
def serialprinter() -> Serial:
return Serial()
@pytest.fixture
def networkprinter():
def networkprinter() -> Network:
return Network()
@pytest.fixture
def fileprinter():
def fileprinter() -> File:
return File()
@pytest.fixture
def lpprinter():
def lpprinter() -> LP:
return LP()

View File

@@ -14,14 +14,14 @@ import pytest
import escpos.escpos as escpos
def test_abstract_base_class_raises():
def test_abstract_base_class_raises() -> None:
"""test whether the abstract base class raises an exception for ESC/POS"""
with pytest.raises(TypeError):
# This call should raise TypeError because of abstractmethod _raw()
escpos.Escpos()
escpos.Escpos() # type: ignore [abstract]
def test_abstract_base_class():
def test_abstract_base_class() -> None:
"""test whether Escpos has the metaclass ABCMeta"""
assert issubclass(escpos.Escpos, object)
assert type(escpos.Escpos) is ABCMeta

View File

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

126
test/test_config.py Normal file
View File

@@ -0,0 +1,126 @@
#!/usr/bin/python
"""tests for config module
:author: `Patrick Kanzler <dev@pkanzler.de>`_
:organization: `python-escpos <https://github.com/python-escpos>`_
:copyright: Copyright (c) 2023 `python-escpos <https://github.com/python-escpos>`_
:license: MIT
"""
import pathlib
import appdirs
import pytest
import escpos.exceptions
def generate_dummy_config(path, content=None):
"""Generate a dummy config in path"""
dummy_config_content = content
if not content:
dummy_config_content = "printer:\n type: Dummy\n"
path.write_text(dummy_config_content)
assert path.read_text() == dummy_config_content
def simple_printer_test(config):
"""Simple test for the dummy printer."""
p = config.printer()
p._raw(b"1234")
assert p.output == b"1234"
def test_config_load_with_invalid_config_yaml(tmp_path):
"""Test the loading of a config with a invalid config file (yaml issue)."""
# generate a dummy config
config_file = tmp_path / "config.yaml"
generate_dummy_config(config_file, content="}invalid}yaml}")
# test the config loading
from escpos import config
c = config.Config()
with pytest.raises(escpos.exceptions.ConfigSyntaxError):
c.load(config_path=config_file)
def test_config_load_with_invalid_config_content(tmp_path):
"""Test the loading of a config with a invalid config file (content issue)."""
# generate a dummy config
config_file = tmp_path / "config.yaml"
generate_dummy_config(
config_file, content="printer:\n type: NoPrinterWithThatName\n"
)
# test the config loading
from escpos import config
c = config.Config()
with pytest.raises(escpos.exceptions.ConfigSyntaxError):
c.load(config_path=config_file)
def test_config_load_with_missing_config(tmp_path):
"""Test the loading of a config that does not exist."""
# test the config loading
from escpos import config
c = config.Config()
with pytest.raises(escpos.exceptions.ConfigNotFoundError):
c.load(config_path=tmp_path)
@pytest.mark.skip(
"This test creates in the actual appdir files and is therefore skipped."
)
def test_config_load_from_appdir() -> None:
"""Test the loading of a config in appdir."""
from escpos import config
# generate a dummy config
config_file = (
pathlib.Path(appdirs.user_config_dir(config.Config._app_name))
/ config.Config._config_file
)
generate_dummy_config(config_file)
# test the config loading
c = config.Config()
c.load()
# test the resulting printer object
simple_printer_test(c)
def test_config_load_with_file(tmp_path):
"""Test the loading of a config with a config file."""
# generate a dummy config
config_file = tmp_path / "config.yaml"
generate_dummy_config(config_file)
# test the config loading
from escpos import config
c = config.Config()
c.load(config_path=config_file)
# test the resulting printer object
simple_printer_test(c)
def test_config_load_with_path(tmp_path):
"""Test the loading of a config with a config path."""
# generate a dummy config
config_file = tmp_path / "config.yaml"
generate_dummy_config(config_file)
# test the config loading
from escpos import config
c = config.Config()
c.load(config_path=tmp_path)
# test the resulting printer object
simple_printer_test(c)

View File

@@ -1,38 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""tests for the non-native part of qr()
:author: `Patrick Kanzler <dev@pkanzler.de>`_
:organization: `python-escpos <https://github.com/python-escpos>`_
:copyright: Copyright (c) 2016 `python-escpos <https://github.com/python-escpos>`_
:license: MIT
"""
import mock
import pytest
from PIL import Image
from escpos.printer import Dummy
@mock.patch("escpos.printer.Dummy.image", spec=Dummy)
def test_type_of_object_passed_to_image_function(img_function):
"""
Test the type of object that is passed to the image function during non-native qr-printing.
The type should be PIL.Image
"""
d = Dummy()
d.qr("LoremIpsum")
args, kwargs = img_function.call_args
assert isinstance(args[0], Image.Image)
@pytest.fixture
def instance():
return Dummy()
def test_center(instance):
instance.qr("LoremIpsum", center=True)

View File

@@ -1,14 +1,13 @@
import pytest
import six
from escpos import printer
from escpos.constants import BUZZER
def test_buzzer_function_with_default_params():
def test_buzzer_function_with_default_params() -> None:
instance = printer.Dummy()
instance.buzzer()
expected = BUZZER + six.int2byte(2) + six.int2byte(4)
expected = BUZZER + bytes((2, 4))
assert instance.output == expected
@@ -26,10 +25,10 @@ def test_buzzer_function_with_default_params():
[9, 9],
],
)
def test_buzzer_function(times, duration):
def test_buzzer_function(times: int, duration: int) -> None:
instance = printer.Dummy()
instance.buzzer(times, duration)
expected = BUZZER + six.int2byte(times) + six.int2byte(duration)
expected = BUZZER + bytes((times, duration))
assert instance.output == expected
@@ -46,7 +45,9 @@ def test_buzzer_function(times, duration):
[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()
with pytest.raises(ValueError) as e:
instance.buzzer(times, duration)

View File

@@ -6,7 +6,7 @@ import escpos.printer as printer
from escpos.exceptions import CashDrawerError
def test_raise_CashDrawerError():
def test_raise_CashDrawerError() -> None:
"""should raise an error if the sequence is invalid."""
instance = printer.Dummy()
with pytest.raises(CashDrawerError):

View File

@@ -1,12 +1,10 @@
import six
import escpos.printer as printer
from escpos.constants import GS
def test_cut_without_feed():
def test_cut_without_feed() -> None:
"""Test cut without feeding paper"""
instance = printer.Dummy()
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

View File

@@ -1,7 +1,7 @@
from escpos.printer import Dummy
def test_printer_dummy_clear():
def test_printer_dummy_clear() -> None:
printer = Dummy()
printer.text("Hello")
printer.clear()

View File

@@ -16,7 +16,7 @@ from escpos.exceptions import ImageWidthError
# Raster format print
def test_bit_image_black():
def test_bit_image_black() -> None:
"""
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"
def test_bit_image_white():
def test_bit_image_white() -> None:
"""
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"
def test_bit_image_both():
def test_bit_image_both() -> None:
"""
Test printing black/white bit image (raster)
"""
@@ -48,7 +48,7 @@ def test_bit_image_both():
assert instance.output == b"\x1dv0\x00\x01\x00\x02\x00\xc0\x00"
def test_bit_image_transparent():
def test_bit_image_transparent() -> None:
"""
Test printing black/transparent bit image (raster)
"""
@@ -58,7 +58,7 @@ def test_bit_image_transparent():
# Column format print
def test_bit_image_colfmt_black():
def test_bit_image_colfmt_black() -> None:
"""
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"
def test_bit_image_colfmt_white():
def test_bit_image_colfmt_white() -> None:
"""
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"
def test_bit_image_colfmt_both():
def test_bit_image_colfmt_both() -> None:
"""
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)
"""
@@ -99,7 +99,7 @@ def test_bit_image_colfmt_transparent():
# Graphics print
def test_graphics_black():
def test_graphics_black() -> None:
"""
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
"""
@@ -123,7 +123,7 @@ def test_graphics_white():
)
def test_graphics_both():
def test_graphics_both() -> None:
"""
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
"""
@@ -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.
"""
@@ -162,13 +162,13 @@ def test_large_graphics():
@pytest.fixture
def dummy_with_width():
def dummy_with_width() -> printer.Dummy:
instance = printer.Dummy()
instance.profile.profile_data = {"media": {"width": {"pixels": 384}}}
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.
"""
@@ -180,7 +180,7 @@ def test_width_too_large(dummy_with_width):
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
with pytest.raises(ImageWidthError):

View File

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

View File

@@ -11,14 +11,14 @@
import escpos.printer as printer
def test_function_panel_button_on():
def test_function_panel_button_on() -> None:
"""test the panel button function (enabling) by comparing output"""
instance = printer.Dummy()
instance.panel_buttons()
assert instance.output == b"\x1B\x63\x35\x00"
def test_function_panel_button_off():
def test_function_panel_button_off() -> None:
"""test the panel button function (disabling) by comparing output"""
instance = printer.Dummy()
instance.panel_buttons(False)

View File

@@ -3,7 +3,7 @@
:author: `Michael Billington <michael.billington@gmail.com>`_
:organization: `python-escpos <https://github.com/python-escpos>`_
:copyright: Copyright (c) 2016 `Michael Billington <michael.billington@gmail.com>`_
:copyright: Copyright (c) 2023 `Michael Billington <michael.billington@gmail.com>`_
:license: MIT
"""
@@ -14,7 +14,7 @@ import escpos.printer as printer
from escpos.constants import QR_ECLEVEL_H, QR_MODEL_1
def test_defaults():
def test_defaults() -> None:
"""Test QR code with defaults"""
instance = printer.Dummy()
instance.qr("1234", native=True)
@@ -25,14 +25,14 @@ def test_defaults():
assert instance.output == expected
def test_empty():
def test_empty() -> None:
"""Test QR printing blank code"""
instance = printer.Dummy()
instance.qr("", native=True)
assert instance.output == b""
def test_ec():
def test_ec() -> None:
"""Test QR error correction setting"""
instance = printer.Dummy()
instance.qr("1234", native=True, ec=QR_ECLEVEL_H)
@@ -43,7 +43,7 @@ def test_ec():
assert instance.output == expected
def test_size():
def test_size() -> None:
"""Test QR box size"""
instance = printer.Dummy()
instance.qr("1234", native=True, size=7)
@@ -54,7 +54,7 @@ def test_size():
assert instance.output == expected
def test_model():
def test_model() -> None:
"""Test QR model"""
instance = printer.Dummy()
instance.qr("1234", native=True, model=QR_MODEL_1)
@@ -65,44 +65,28 @@ def test_model():
assert instance.output == expected
def test_invalid_ec():
def test_invalid_ec() -> None:
"""Test invalid QR error correction"""
instance = printer.Dummy()
with pytest.raises(ValueError):
instance.qr("1234", native=True, ec=-1)
def test_invalid_size():
def test_invalid_size() -> None:
"""Test invalid QR size"""
instance = printer.Dummy()
with pytest.raises(ValueError):
instance.qr("1234", native=True, size=0)
def test_invalid_model():
def test_invalid_model() -> None:
"""Test invalid QR model"""
instance = printer.Dummy()
with pytest.raises(ValueError):
instance.qr("1234", native=True, model="Hello")
@pytest.mark.skip("this test has to be debugged")
def test_image():
"""Test QR as image"""
instance = printer.Dummy()
instance.qr("1", native=False, size=1)
print(instance.output)
expected = (
b"\x1bt\x00\n"
b"\x1dv0\x00\x03\x00\x17\x00\x00\x00\x00\x7f]\xfcA\x19\x04]it]et"
b"]ItA=\x04\x7fU\xfc\x00\x0c\x00y~t4\x7f =\xa84j\xd9\xf0\x05\xd4\x90\x00"
b"i(\x7f<\xa8A \xd8]'\xc4]y\xf8]E\x80Ar\x94\x7fR@\x00\x00\x00"
b"\n\n"
)
assert instance.output == expected
def test_image_invalid_model():
def test_image_invalid_model() -> None:
"""Test unsupported QR model as image"""
instance = printer.Dummy()
with pytest.raises(ValueError):
@@ -114,6 +98,6 @@ def instance():
return printer.Dummy()
def test_center_not_implementer(instance):
def test_center_not_implementer(instance: printer.Dummy) -> None:
with pytest.raises(NotImplementedError):
instance.qr("test", center=True, native=True)

View File

@@ -0,0 +1,99 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""tests for the non-native part of qr()
:author: `Patrick Kanzler <dev@pkanzler.de>`_
:organization: `python-escpos <https://github.com/python-escpos>`_
:copyright: Copyright (c) 2023 `python-escpos <https://github.com/python-escpos>`_
:license: MIT
"""
import warnings
import mock
import pytest
from PIL import Image
from escpos.printer import Dummy
def test_image() -> None:
"""Test QR as image"""
instance = Dummy()
image_arguments = {
"high_density_vertical": True,
"high_density_horizontal": True,
"impl": "bitImageRaster",
"fragment_height": 960,
"center": False,
}
instance.qr("1", native=False, image_arguments=image_arguments, size=1)
print(instance.output)
expected = (
b"\x1bt\x00\n"
b"\x1dv0\x00\x03\x00\x17\x00\x00\x00\x00\x7f\x1d\xfcAu\x04]\x1dt]et"
b"]%tAI\x04\x7fU\xfc\x00 \x00}\xca\xa8h\xdf u\x95\x80x/ \x0b\xf4\x98\x00"
b"T\x90\x7fzxA\x00\xd0]zp]o ]u\x80Ao(\x7fd\x90\x00\x00\x00"
b"\n\n"
)
assert instance.output == expected
@mock.patch("escpos.printer.Dummy.image", spec=Dummy)
def test_type_of_object_passed_to_image_function(img_function):
"""
Test the type of object that is passed to the image function during non-native qr-printing.
The type should be PIL.Image
"""
d = Dummy()
d.qr("LoremIpsum")
args, kwargs = img_function.call_args
assert isinstance(args[0], Image.Image)
@mock.patch("escpos.printer.Dummy.image", spec=Dummy)
def test_parameter_image_arguments_passed_to_image_function(img_function):
"""Test the parameter passed to non-native qr printing."""
d = Dummy()
d.qr(
"LoremIpsum",
native=False,
image_arguments={
"impl": "bitImageColumn",
"high_density_vertical": False,
"center": True,
},
)
args, kwargs = img_function.call_args
assert "impl" in kwargs
assert kwargs["impl"] == "bitImageColumn"
assert "high_density_vertical" in kwargs
assert kwargs["high_density_vertical"] is False
assert "center" in kwargs
assert kwargs["center"]
@pytest.fixture
def instance():
return Dummy()
def test_center(instance):
"""Test printing qr codes."""
instance.qr("LoremIpsum", center=True)
def test_deprecated_arguments(instance):
"""Test deprecation warning."""
with warnings.catch_warnings(record=True) as w:
# cause all warnings to always be triggered
warnings.simplefilter("always")
instance.qr(
"LoremIpsum",
impl="bitImageRaster",
image_arguments={"impl": "bitImageColumn"},
)
assert issubclass(w[-1].category, DeprecationWarning)
assert "deprecated" in str(w[-1].message)

View File

@@ -1,12 +1,13 @@
from typing import Optional
import pytest
import six
import escpos.printer as printer
from escpos.constants import SET_FONT, TXT_NORMAL, TXT_SIZE, TXT_STYLE
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"""
instance = printer.Dummy()
instance.set_with_default()
@@ -26,7 +27,7 @@ def test_default_values_with_default():
assert instance.output == b"".join(expected_sequence)
def test_default_values():
def test_default_values() -> None:
"""Default test"""
instance = printer.Dummy()
instance.set()
@@ -37,7 +38,7 @@ def test_default_values():
# Size tests
def test_set_size_2h():
def test_set_size_2h() -> None:
instance = printer.Dummy()
instance.set_with_default(double_height=True)
@@ -56,7 +57,7 @@ def test_set_size_2h():
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.set(double_height=True)
@@ -68,7 +69,7 @@ def test_set_size_2h_no_default():
assert instance.output == b"".join(expected_sequence)
def test_set_size_2w():
def test_set_size_2w() -> None:
instance = printer.Dummy()
instance.set_with_default(double_width=True)
@@ -87,7 +88,7 @@ def test_set_size_2w():
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.set(double_width=True)
@@ -99,7 +100,7 @@ def test_set_size_2w_no_default():
assert instance.output == b"".join(expected_sequence)
def test_set_size_2x():
def test_set_size_2x() -> None:
instance = printer.Dummy()
instance.set_with_default(double_height=True, double_width=True)
@@ -118,7 +119,7 @@ def test_set_size_2x():
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.set(double_width=True, double_height=True)
@@ -130,13 +131,13 @@ def test_set_size_2x_no_default():
assert instance.output == b"".join(expected_sequence)
def test_set_size_custom():
def test_set_size_custom() -> None:
instance = printer.Dummy()
instance.set_with_default(custom_size=True, width=8, height=7)
expected_sequence = (
TXT_SIZE, # Custom text size, no normal reset
six.int2byte(TXT_STYLE["width"][8] + TXT_STYLE["height"][7]),
bytes((TXT_STYLE["width"][8] + TXT_STYLE["height"][7],)),
TXT_STYLE["flip"][False], # Flip OFF
TXT_STYLE["smooth"][False], # Smooth OFF
TXT_STYLE["bold"][False], # Bold OFF
@@ -151,13 +152,13 @@ def test_set_size_custom():
@pytest.mark.parametrize("width", [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.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]),
bytes((TXT_STYLE["width"][width] + TXT_STYLE["height"][height],)),
)
assert instance.output == b"".join(expected_sequence)
@@ -165,7 +166,9 @@ def test_set_size_custom_no_default(width, height):
@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):
def test_set_size_custom_invalid_input(
width: Optional[int], height: Optional[int]
) -> None:
instance = printer.Dummy()
with pytest.raises(SetVariableError):
instance.set(custom_size=True, width=width, height=height)
@@ -174,7 +177,7 @@ def test_set_size_custom_invalid_input(width, height):
# Flip
def test_set_flip():
def test_set_flip() -> None:
instance = printer.Dummy()
instance.set_with_default(flip=True)
@@ -193,7 +196,7 @@ def test_set_flip():
assert instance.output == b"".join(expected_sequence)
def test_set_flip_no_default():
def test_set_flip_no_default() -> None:
instance = printer.Dummy()
instance.set(flip=True)
@@ -205,7 +208,7 @@ def test_set_flip_no_default():
# Smooth
def test_smooth():
def test_smooth() -> None:
instance = printer.Dummy()
instance.set_with_default(smooth=True)
@@ -227,7 +230,7 @@ def test_smooth():
# Type
def test_set_bold():
def test_set_bold() -> None:
instance = printer.Dummy()
instance.set_with_default(bold=True)
@@ -246,7 +249,7 @@ def test_set_bold():
assert instance.output == b"".join(expected_sequence)
def test_set_underline():
def test_set_underline() -> None:
instance = printer.Dummy()
instance.set_with_default(underline=1)
@@ -265,7 +268,7 @@ def test_set_underline():
assert instance.output == b"".join(expected_sequence)
def test_set_underline2():
def test_set_underline2() -> None:
instance = printer.Dummy()
instance.set_with_default(underline=2)
@@ -287,7 +290,7 @@ def test_set_underline2():
# Align
def test_align_center():
def test_align_center() -> None:
instance = printer.Dummy()
instance.set_with_default(align="center")
@@ -306,7 +309,7 @@ def test_align_center():
assert instance.output == b"".join(expected_sequence)
def test_align_right():
def test_align_right() -> None:
instance = printer.Dummy()
instance.set_with_default(align="right")
@@ -328,7 +331,7 @@ def test_align_right():
# Densities
def test_densities():
def test_densities() -> None:
for density in range(8):
instance = printer.Dummy()
instance.set_with_default(density=density)
@@ -352,7 +355,7 @@ def test_densities():
# Invert
def test_invert():
def test_invert() -> None:
instance = printer.Dummy()
instance.set_with_default(invert=True)

View File

@@ -11,16 +11,16 @@ def instance():
return printer.Dummy()
def test_soft_barcode_ean8_invalid(instance):
def test_soft_barcode_ean8_invalid(instance: printer.Dummy) -> None:
"""test with an invalid barcode"""
with pytest.raises(barcode.errors.BarcodeError):
instance.barcode("1234", "ean8", force_software=True)
def test_soft_barcode_ean8(instance):
def test_soft_barcode_ean8(instance: printer.Dummy) -> None:
"""test with a valid ean8 barcode"""
instance.barcode("1234567", "ean8", force_software=True)
def test_soft_barcode_ean8_nocenter(instance):
def test_soft_barcode_ean8_nocenter(instance: printer.Dummy) -> None:
instance.barcode("1234567", "ean8", align_ct=False, force_software=True)

View File

@@ -10,26 +10,25 @@
import hypothesis.strategies as st
import mock
import pytest
from hypothesis import assume, given
from hypothesis import given
from escpos.printer import Dummy
def get_printer():
def get_printer() -> Dummy:
return Dummy(magic_encode_args={"disabled": True, "encoding": "CP437"})
@given(text=st.text())
def test_text(text):
def test_text(text: str):
"""Test that text() calls the MagicEncode object."""
instance = get_printer()
instance.magic.write = mock.Mock()
instance.text(text)
instance.magic.write.assert_called_with(text)
with mock.patch.object(instance.magic, "write") as write:
instance.text(text)
write.assert_called_with(text)
def test_block_text():
def test_block_text() -> None:
printer = get_printer()
printer.block_text(
"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.textln("hello, world")
assert printer.output == b"hello, world\n"
def test_textln_empty():
def test_textln_empty() -> None:
printer = get_printer()
printer.textln()
assert printer.output == b"\n"
def test_ln():
def test_ln() -> None:
printer = get_printer()
printer.ln()
assert printer.output == b"\n"
def test_multiple_ln():
def test_multiple_ln() -> None:
printer = get_printer()
printer.ln(3)
assert printer.output == b"\n\n\n"

View File

@@ -3,19 +3,19 @@ import pytest
from escpos.printer import Dummy
def test_line_spacing_code_gen():
def test_line_spacing_code_gen() -> None:
printer = Dummy()
printer.line_spacing(10)
assert printer.output == b"\x1b3\n"
def test_line_spacing_rest():
def test_line_spacing_rest() -> None:
printer = Dummy()
printer.line_spacing()
assert printer.output == b"\x1b2"
def test_line_spacing_error_handling():
def test_line_spacing_error_handling() -> None:
printer = Dummy()
with pytest.raises(ValueError):
printer.line_spacing(99, divisor=44)

View File

@@ -7,11 +7,12 @@ converted to ESC/POS column & raster formats.
:copyright: Copyright (c) 2016 `Michael Billington <michael.billington@gmail.com>`_
:license: MIT
"""
from typing import List
from escpos.image import EscposImage
def test_image_black():
def test_image_black() -> None:
"""
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"])
def test_image_black_transparent():
def test_image_black_transparent() -> None:
"""
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
"""
@@ -39,7 +40,7 @@ def test_image_black_white():
)
def test_image_white():
def test_image_white() -> None:
"""
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"])
def test_split():
def test_split() -> None:
"""
test whether the split-function works as expected
"""
@@ -62,12 +63,12 @@ def test_split():
def _load_and_check_img(
filename,
width_expected,
height_expected,
raster_format_expected,
column_format_expected,
):
filename: str,
width_expected: int,
height_expected: int,
raster_format_expected: bytes,
column_format_expected: List[bytes],
) -> None:
"""
Load an image, and test whether raster & column formatted output, sizes, etc match expectations.
"""

View File

@@ -11,7 +11,7 @@
import escpos.printer as printer
def test_instantiation():
def test_instantiation() -> None:
"""test the instantiation of a escpos-printer class and basic printing"""
instance = printer.Dummy()
instance.text("This is a test\n")

View File

@@ -13,7 +13,7 @@ import tempfile
import pytest
def test_instantiation():
def test_instantiation() -> None:
"""test the instantiation of a escpos-printer class"""
# inject an environment variable that points to an empty capabilities file
os.environ["ESCPOS_CAPABILITIES_FILE"] = tempfile.NamedTemporaryFile().name

View File

@@ -13,7 +13,8 @@ import hypothesis.strategies as st
import pytest
from hypothesis import example, given
from escpos.exceptions import CharCodeError, Error
from escpos import printer
from escpos.exceptions import Error
from escpos.katakana import encode_katakana
from escpos.magicencode import Encoder, MagicEncode
@@ -23,23 +24,23 @@ class TestEncoder:
Tests the single encoders.
"""
def test_can_encode(self):
def test_can_encode(self) -> None:
assert not Encoder({"CP437": 1}).can_encode("CP437", "")
assert Encoder({"CP437": 1}).can_encode("CP437", "á")
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 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})
# desired behavior would be that the encoder always stays in the lower
# available codepages if possible
for character in ("Á", "É", "Í", "Ó", "Ú"):
assert enc.find_suitable_encoding(character) == "CP857"
def test_get_encoding(self):
def test_get_encoding(self) -> None:
with pytest.raises(ValueError):
Encoder({}).get_encoding_name("latin1")
@@ -54,7 +55,7 @@ class TestMagicEncode:
Test initialization.
"""
def test_disabled_requires_encoding(self, driver):
def test_disabled_requires_encoding(self, driver: printer.Dummy) -> None:
"""
Test that disabled without encoder raises an error.
@@ -64,33 +65,33 @@ class TestMagicEncode:
MagicEncode(driver, disabled=True)
class TestWriteWithEncoding:
def test_init_from_none(self, driver):
def test_init_from_none(self, driver: printer.Dummy) -> None:
encode = MagicEncode(driver, encoding=None)
encode.write_with_encoding("CP858", "€ ist teuro.")
assert driver.output == b"\x1bt\x13\xd5 ist teuro."
def test_change_from_another(self, driver):
def test_change_from_another(self, driver: printer.Dummy) -> None:
encode = MagicEncode(driver, encoding="CP437")
encode.write_with_encoding("CP858", "€ ist teuro.")
assert driver.output == b"\x1bt\x13\xd5 ist teuro."
def test_no_change(self, driver):
def test_no_change(self, driver: printer.Dummy) -> None:
encode = MagicEncode(driver, encoding="CP858")
encode.write_with_encoding("CP858", "€ ist teuro.")
assert driver.output == b"\xd5 ist teuro."
class TestWrite:
def test_write(self, driver):
def test_write(self, driver: printer.Dummy) -> None:
encode = MagicEncode(driver)
encode.write("€ ist teuro.")
assert driver.output == b"\x1bt\x0f\xa4 ist teuro."
def test_write_disabled(self, driver):
def test_write_disabled(self, driver: printer.Dummy) -> None:
encode = MagicEncode(driver, encoding="CP437", disabled=True)
encode.write("€ ist teuro.")
assert driver.output == b"? ist teuro."
def test_write_no_codepage(self, driver):
def test_write_no_codepage(self, driver: printer.Dummy) -> None:
encode = MagicEncode(
driver,
defaultsymbol="_",
@@ -101,7 +102,7 @@ class TestMagicEncode:
assert driver.output == b"_ ist teuro."
class TestForceEncoding:
def test(self, driver):
def test(self, driver: printer.Dummy) -> None:
encode = MagicEncode(driver)
encode.force_encoding("CP437")
assert driver.output == b"\x1bt\x00"
@@ -122,9 +123,9 @@ class TestKatakana:
@example("カタカナ")
@example("あいうえお")
@example("ハンカクカタカナ")
def test_accept(self, text):
def test_accept(self, text: str) -> None:
encode_katakana(text)
def test_result(self):
def test_result(self) -> None:
assert encode_katakana("カタカナ") == b"\xb6\xc0\xb6\xc5"
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
WHEN it is not initialized
@@ -28,7 +28,7 @@ def test_device_not_initialized(cupsprinter):
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
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)
def test_open_not_raise_exception(cupsprinter, caplog):
def test_open_not_raise_exception(cupsprinter, caplog) -> None:
"""
GIVEN a cups printer object
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
def test_open(cupsprinter, caplog, mocker):
def test_open(cupsprinter, caplog, mocker) -> None:
"""
GIVEN a cups printer object and a mocked pycups device
WHEN a valid connection to a device is opened
@@ -74,7 +74,7 @@ def test_open(cupsprinter, caplog, mocker):
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
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
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
WHEN closing connection before send the buffer
@@ -133,7 +133,7 @@ def test_send_on_close(cupsprinter, mocker):
assert cupsprinter.pending_job is False
def test_raw_raise_exception(cupsprinter):
def test_raw_raise_exception(cupsprinter) -> None:
"""
GIVEN a cups printer object
WHEN passing a non byte string to _raw()
@@ -145,7 +145,7 @@ def test_raw_raise_exception(cupsprinter):
assert cupsprinter.pending_job is False
def test_raw(cupsprinter):
def test_raw(cupsprinter) -> None:
"""
GIVEN a cups printer object
WHEN passing a byte string to _raw()
@@ -156,7 +156,7 @@ def test_raw(cupsprinter):
assert cupsprinter.tmpfile.read() == b"Test"
def test_printers_no_device(cupsprinter):
def test_printers_no_device(cupsprinter) -> None:
"""
GIVEN a cups printer object
WHEN device is None
@@ -166,11 +166,11 @@ def test_printers_no_device(cupsprinter):
assert cupsprinter.printers == {}
def test_read_no_device(cupsprinter):
def test_read_no_device(cupsprinter) -> None:
"""
GIVEN a cups printer object
WHEN device is None
THEN check the return value is []
THEN check the return value is b'8'
"""
cupsprinter.device = None
assert cupsprinter._read() == []
assert cupsprinter._read() == b"8"

View File

@@ -14,16 +14,16 @@ import escpos
import escpos.exceptions
def test_raise_error_wrongly():
def test_raise_error_wrongly() -> None:
"""raise error the wrong way
should reproduce https://github.com/python-escpos/python-escpos/issues/257
"""
with pytest.raises(AttributeError):
raise escpos.Error("This should raise an AttributeError.")
raise escpos.Error("This should raise an AttributeError.") # type: ignore [attr-defined]
def tests_raise_error():
def tests_raise_error() -> None:
"""raise error the right way"""
with pytest.raises(escpos.exceptions.Error):
raise escpos.exceptions.Error("This should raise an error.")

View File

@@ -12,7 +12,7 @@ import escpos.escpos as escpos
import escpos.printer as printer
def test_with_statement():
def test_with_statement() -> None:
"""Use with statement
.. todo:: Extend these tests as they don't really do anything at the moment"""

View File

@@ -21,7 +21,7 @@ deps = jaconv
pytest-cov
pytest-mock
hypothesis>=6.83
python-barcode
python-barcode>=0.15.0,<1
extras = all
commands = pytest
passenv = ESCPOS_CAPABILITIES_PICKLE_DIR, ESCPOS_CAPABILITIES_FILE, CI, TRAVIS, TRAVIS_*, APPVEYOR, APPVEYOR_*, CODECOV_*
@@ -32,7 +32,7 @@ basepython = python
changedir = doc
deps = sphinx>=7.2.3
setuptools_scm
python-barcode
python-barcode>=0.15.0,<1
sphinx-argparse
sphinxcontrib-spelling>=8.0.0
sphinxcontrib.datatemplates
@@ -58,6 +58,7 @@ deps = mypy
types-Pillow
types-pyserial
types-pywin32>=306.0.0.6
types-flask
hypothesis>=6.83
jaconv
commands = mypy src test
commands = mypy src test examples