Drop Py37, improve typing and docstrings (#544)

Drops Py3.7, improves typing and adds a mypy config, improves the docstrings and isorts the imports.

* configure isort
* sort with isort
* add github action
* enable flake8-docstrings
* fix docstrings
* add mypy env
* no implicit optional
* add type for raw
* add some type hints
This commit is contained in:
Patrick Kanzler 2023-08-15 01:03:36 +02:00 committed by GitHub
parent 2b62c8e28d
commit fbabd8ed88
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 465 additions and 345 deletions

View File

@ -30,6 +30,11 @@ jobs:
python -m pip install --upgrade pip python -m pip install --upgrade pip
pip install flake8 pytest tox tox-gh-actions pip install flake8 pytest tox tox-gh-actions
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
- name: Check sorting of imports
uses: isort/isort-action@master
with:
requirementsFiles: "requirements.txt doc/requirements.txt"
sortPaths: "./doc ./src ./examples ./test ./setup.py"
- name: Lint with flake8 - name: Lint with flake8
run: | run: |
# stop the build if there are Python syntax errors or undefined names # stop the build if there are Python syntax errors or undefined names
@ -50,4 +55,4 @@ jobs:
files: ./coverage.xml,!./cache files: ./coverage.xml,!./cache
flags: unittests flags: unittests
name: coverage-tox-${{ matrix.python-version }} name: coverage-tox-${{ matrix.python-version }}
verbose: true verbose: true

View File

@ -12,9 +12,8 @@
# All configuration values have a default; values that are commented out # All configuration values have a default; values that are commented out
# serve to show the default. # serve to show the default.
import sys
import os import os
import sys
from importlib.metadata import version as imp_version from importlib.metadata import version as imp_version
on_rtd = os.getenv("READTHEDOCS") == "True" on_rtd = os.getenv("READTHEDOCS") == "True"

View File

@ -1,6 +1,6 @@
"""Example for printing barcodes."""
from escpos.printer import Usb from escpos.printer import Usb
# Adapt to your needs # Adapt to your needs
p = Usb(0x0416, 0x5011, profile="TM-T88II") p = Usb(0x0416, 0x5011, profile="TM-T88II")

View File

@ -1,23 +1,24 @@
"""Prints code page tables. """Prints code page tables."""
"""
import sys
import six import six
import sys
from escpos import printer from escpos import printer
from escpos.constants import ( from escpos.constants import (
CODEPAGE_CHANGE, CODEPAGE_CHANGE,
ESC,
CTL_LF,
CTL_FF,
CTL_CR, CTL_CR,
CTL_FF,
CTL_HT, CTL_HT,
CTL_LF,
CTL_VT, CTL_VT,
ESC,
) )
def main(): def main():
"""Init printer and print codepage tables."""
dummy = printer.Dummy() dummy = printer.Dummy()
dummy.hw("init") dummy.hw("init")
@ -34,6 +35,7 @@ def main():
def print_codepage(printer, codepage): def print_codepage(printer, 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 + six.int2byte(codepage))

View File

@ -1,12 +1,15 @@
from escpos.printer import CupsPrinter """Example for a flask application."""
from flask import Flask from flask import Flask
from escpos.printer import CupsPrinter
# Initialize Flask app # Initialize Flask app
app = Flask(__name__) app = Flask(__name__)
@app.route("/", methods=["GET"]) @app.route("/", methods=["GET"])
def do_print(): def do_print():
"""Print."""
# p = Usb(0x04b8, 0x0e28, 0) # p = Usb(0x04b8, 0x0e28, 0)
p = CupsPrinter(host="localhost", port=631, printer_name="TM-T20III") p = CupsPrinter(host="localhost", port=631, printer_name="TM-T20III")
p.text("Hello World\n") p.text("Hello World\n")

View File

@ -3,7 +3,6 @@ argcomplete==3.0.8
blinker==1.6.2 blinker==1.6.2
click==8.1.3 click==8.1.3
Flask==2.3.2 Flask==2.3.2
future==0.18.3
itsdangerous==2.1.2 itsdangerous==2.1.2
Jinja2==3.1.2 Jinja2==3.1.2
MarkupSafe==2.1.2 MarkupSafe==2.1.2

View File

@ -1,9 +1,11 @@
"""Print example QR codes."""
import sys import sys
from escpos.printer import Usb from escpos.printer import Usb
def usage(): def usage():
"""Print information on usage."""
print("usage: qr_code.py <content>") print("usage: qr_code.py <content>")

View File

@ -1,6 +1,6 @@
"""Example file for software barcodes."""
from escpos.printer import Usb from escpos.printer import Usb
# Adapt to your needs # Adapt to your needs
p = Usb(0x0416, 0x5011, profile="POS-5890") p = Usb(0x0416, 0x5011, profile="POS-5890")

View File

@ -1,28 +1,29 @@
#!/usr/bin/python #!/usr/bin/python
"""Weather forecast example.
Adapted script from Adafruit
Weather forecast for Raspberry Pi w/Adafruit Mini Thermal Printer.
Retrieves data from DarkSky.net's API, prints current conditions and
forecasts for next two days.
Weather example using nice bitmaps.
Written by Adafruit Industries. MIT license.
Adapted and enhanced for escpos library by MrWunderbar666
Icons taken from https://adamwhitcroft.com/climacons/
Check out his github: https://github.com/AdamWhitcroft/climacons
"""
# Adapted script from Adafruit
# Weather forecast for Raspberry Pi w/Adafruit Mini Thermal Printer.
# Retrieves data from DarkSky.net's API, prints current conditions and
# forecasts for next two days.
# Weather example using nice bitmaps.
# Written by Adafruit Industries. MIT license.
# Adapted and enhanced for escpos library by MrWunderbar666
# Icons taken from https://adamwhitcroft.com/climacons/
# Check out his github: https://github.com/AdamWhitcroft/climacons
from datetime import datetime
import calendar import calendar
import urllib
import json import json
import time
import os import os
import time
import urllib
from datetime import datetime
from escpos.printer import Usb from escpos.printer import Usb
""" Setting up the main pathing """ """Set up the main pathing."""
this_dir, this_filename = os.path.split(__file__) this_dir, this_filename = os.path.split(__file__)
GRAPHICS_PATH = os.path.join(this_dir, "graphics/climacons/") GRAPHICS_PATH = os.path.join(this_dir, "graphics/climacons/")
@ -38,13 +39,14 @@ LONG = "114.189945" # Your Location
def forecast_icon(idx): def forecast_icon(idx):
"""Get right icon for forecast."""
icon = data["daily"]["data"][idx]["icon"] icon = data["daily"]["data"][idx]["icon"]
image = GRAPHICS_PATH + icon + ".png" image = GRAPHICS_PATH + icon + ".png"
return image return image
# Dumps one forecast line to the printer
def forecast(idx): def forecast(idx):
"""Dump one forecast line to the printer."""
date = datetime.fromtimestamp(int(data["daily"]["data"][idx]["time"])) date = datetime.fromtimestamp(int(data["daily"]["data"][idx]["time"]))
day = calendar.day_name[date.weekday()] day = calendar.day_name[date.weekday()]
lo = data["daily"]["data"][idx]["temperatureMin"] lo = data["daily"]["data"][idx]["temperatureMin"]
@ -73,6 +75,7 @@ def forecast(idx):
def icon(): def icon():
"""Get icon."""
icon = data["currently"]["icon"] icon = data["currently"]["icon"]
image = GRAPHICS_PATH + icon + ".png" image = GRAPHICS_PATH + icon + ".png"
return image return image

View File

@ -1,3 +1,15 @@
[tool.black] [tool.black]
extend-exclude = 'capabilities-data' extend-exclude = 'capabilities-data'
[tool.isort]
profile = "black"
[tool.pytest.ini_options]
minversion = "6.0"
addopts = "--doctest-modules --cov escpos --cov-report=xml"
testpaths = [
"test",
"src",
"src/escpos",
"escpos",
]

View File

@ -31,7 +31,7 @@ project_urls =
Release Notes = https://github.com/python-escpos/python-escpos/releases Release Notes = https://github.com/python-escpos/python-escpos/releases
[options] [options]
python_requires = >=3.6 python_requires = >=3.8
zip_safe = false zip_safe = false
include_package_data = true include_package_data = true
install_requires = install_requires =
@ -46,7 +46,6 @@ install_requires =
PyYAML PyYAML
argparse argparse
argcomplete argcomplete
future
importlib_resources importlib_resources
setup_requires = setuptools_scm setup_requires = setuptools_scm
tests_require = tests_require =
@ -65,4 +64,3 @@ tests_require =
exclude = .git,.tox,.github,.eggs,__pycache__,doc/conf.py,build,dist,capabilities-data,test,src/escpos/constants.py exclude = .git,.tox,.github,.eggs,__pycache__,doc/conf.py,build,dist,capabilities-data,test,src/escpos/constants.py
max-line-length = 120 max-line-length = 120
extend-ignore = E203, W503 extend-ignore = E203, W503
# future-imports = absolute_import, division, print_function, unicode_literals # we are not there yet

View File

@ -1,9 +1,10 @@
#!/usr/bin/env python #!/usr/bin/env python
"""Setup script for python package."""
import os import os
import sys import sys
from setuptools import find_packages, setup
from setuptools import find_packages, setup
base_dir = os.path.dirname(__file__) base_dir = os.path.dirname(__file__)
src_dir = os.path.join(base_dir, "src") src_dir = os.path.join(base_dir, "src")
@ -14,14 +15,18 @@ sys.path.insert(0, src_dir)
def read(fname): def read(fname):
"""read file from same path as setup.py""" """Read file from same path as setup.py."""
return open(os.path.join(os.path.dirname(__file__), fname)).read() return open(os.path.join(os.path.dirname(__file__), fname)).read()
setuptools_scm_template = """\ setuptools_scm_template = """\
# coding: utf-8 #!/usr/bin/python
# file generated by setuptools_scm # -*- coding: utf-8 -*-
# don't change, don't track in version control \"\"\"Version identifier.
file generated by setuptools_scm
don't change, don't track in version control
\"\"\"
version = '{version}' version = '{version}'
""" """

View File

@ -1,7 +1,5 @@
# -*- 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"]

View File

@ -1,20 +1,19 @@
import re """Handler for capabilities data."""
from os import environ, path
import atexit import atexit
import pickle
import logging import logging
import pickle
import platform
import re
import time import time
import importlib_resources from contextlib import ExitStack
from os import environ, path
from tempfile import mkdtemp
from typing import Any, Dict, Optional
import importlib_resources
import six import six
import yaml import yaml
from contextlib import ExitStack
from tempfile import mkdtemp
import platform
from typing import Any, Dict
logging.basicConfig() logging.basicConfig()
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -60,9 +59,7 @@ PROFILES: Dict[str, Any] = CAPABILITIES["profiles"]
class NotSupported(Exception): class NotSupported(Exception):
"""Raised if a requested feature is not supported by the """Raised if a requested feature is not supported by the printer profile."""
printer profile.
"""
pass pass
@ -80,11 +77,13 @@ class BaseProfile(object):
profile_data: Dict[str, Any] = {} profile_data: Dict[str, Any] = {}
def __getattr__(self, name): def __getattr__(self, name):
"""Get a data element from the profile."""
return self.profile_data[name] return self.profile_data[name]
def get_font(self, font) -> int: def get_font(self, font) -> int:
"""Return the escpos index for `font`. Makes sure that """Return the escpos index for `font`.
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 six.text_type(font) in self.fonts:
@ -107,9 +106,10 @@ class BaseProfile(object):
return {v: k for k, v in self.codePages.items()} return {v: k for k, v in self.codePages.items()}
def get_profile(name: str = None, **kwargs): def get_profile(name: Optional[str] = None, **kwargs):
"""Get the profile by name; if no name is given, return the """Get a profile by name.
default profile.
If no name is given, return the default profile.
""" """
if isinstance(name, Profile): if isinstance(name, Profile):
return name return name
@ -122,7 +122,9 @@ CLASS_CACHE = {}
def get_profile_class(name: str): def get_profile_class(name: str):
"""For the given profile name, load the data from the external """Load a profile class.
For the given profile name, load the data from the external
database, then generate dynamically a class. database, then generate dynamically a class.
""" """
if name not in CLASS_CACHE: if name not in CLASS_CACHE:
@ -136,6 +138,7 @@ def get_profile_class(name: str):
def clean(s): def clean(s):
"""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)
# Remove leading characters until we find a letter or underscore # Remove leading characters until we find a letter or underscore
@ -144,17 +147,20 @@ def clean(s):
class Profile(get_profile_class("default")): class Profile(get_profile_class("default")):
""" """Profile class for user usage.
For users, who want to provide their profile
For users, who want to provide their own profile.
""" """
def __init__(self, columns=None, features=None): def __init__(self, columns=None, features=None):
"""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):
"""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

@ -1,6 +1,6 @@
#!/usr/bin/env python #!/usr/bin/env python
# PYTHON_ARGCOMPLETE_OK # PYTHON_ARGCOMPLETE_OK
""" CLI """CLI.
This module acts as a command line interface for python-escpos. It mirrors This module acts as a command line interface for python-escpos. It mirrors
closely the available ESCPOS commands while adding a couple extra ones for convenience. closely the available ESCPOS commands while adding a couple extra ones for convenience.
@ -18,15 +18,18 @@ except ImportError:
# this CLI works nevertheless without argcomplete # this CLI works nevertheless without argcomplete
pass # noqa pass # noqa
import sys import sys
import six import six
from . import config
from . import version from . import config, 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):
"""Used as a type in argparse so that we get back a proper """Convert string to Bool.
bool instead of always True
Used as a type in argparse so that we get back a proper
bool instead of always True.
""" """
return string.lower() in ("y", "yes", "1", "true") return string.lower() in ("y", "yes", "1", "true")
@ -452,13 +455,11 @@ ESCPOS_COMMANDS = [
def main(): def main():
""" """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
line arguments. Called when run from a CLI. line arguments. Called when run from a CLI.
""" """
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description="CLI for python-escpos", description="CLI for python-escpos",
epilog="Printer configuration is defined in the python-escpos config" epilog="Printer configuration is defined in the python-escpos config"
@ -569,8 +570,9 @@ def main():
def demo(printer, **kwargs): def demo(printer, **kwargs):
""" """Print demos.
Prints demos. Called when CLI is passed `demo`. This function
Called when CLI is passed `demo`. This function
uses the DEMO_FUNCTIONS dictionary. uses the DEMO_FUNCTIONS dictionary.
:param printer: A printer from escpos.printer :param printer: A printer from escpos.printer

View File

@ -1,23 +1,31 @@
"""Helper module for codepage handling."""
from .capabilities import CAPABILITIES from .capabilities import CAPABILITIES
class CodePageManager: class CodePageManager:
"""Holds information about all the code pages (as defined """Holds information about all the code pages.
in escpos-printer-db).
Information as defined in escpos-printer-db.
""" """
def __init__(self, data): def __init__(self, data):
"""Initialize codepage manager."""
self.data = data self.data = data
def get_all(self): def get_all(self):
"""Get list of all codepages."""
return self.data.values() return self.data.values()
@staticmethod @staticmethod
def get_encoding_name(encoding): def get_encoding_name(encoding):
# TODO resolve the encoding alias """Get encoding name.
.. todo:: Resolve the encoding alias.
"""
return encoding.upper() return encoding.upper()
def get_encoding(self, encoding): def get_encoding(self, encoding):
"""Return the encoding data."""
return self.data[encoding] return self.data[encoding]

View File

@ -1,16 +1,13 @@
""" ESC/POS configuration manager. """ESC/POS configuration manager.
This module contains the implementations of abstract base class :py:class:`Config`. This module contains the implementations of abstract base class :py:class:`Config`.
""" """
import os import os
import appdirs import appdirs
import yaml import yaml
from . import printer from . import exceptions, printer
from . import exceptions
class Config(object): class Config(object):
@ -48,13 +45,11 @@ class Config(object):
self._printer_config = None self._printer_config = None
def load(self, config_path=None): def load(self, config_path=None):
"""Load and parse the configuration file using pyyaml """Load and parse the configuration file using pyyaml.
:param config_path: An optional file path, file handle, or byte string :param config_path: An optional file path, file handle, or byte string
for the configuration file. for the configuration file.
""" """
self._reset_config() self._reset_config()
if not config_path: if not config_path:
@ -96,8 +91,9 @@ class Config(object):
self._has_loaded = True self._has_loaded = True
def printer(self): def printer(self):
"""Returns a printer that was defined in the config, or throws an """Return a printer that was defined in the config.
exception.
Throw an exception on error.
This method loads the default config if one hasn't beeen already loaded. This method loads the default config if one hasn't beeen already loaded.

View File

@ -1,6 +1,6 @@
#!/usr/bin/python #!/usr/bin/python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" Main class """Main class.
This module contains the abstract base class :py:class:`Escpos`. This module contains the abstract base class :py:class:`Escpos`.
@ -11,80 +11,88 @@ This module contains the abstract base class :py:class:`Escpos`.
""" """
import qrcode
import textwrap import textwrap
import six 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 Union
import barcode import barcode
import qrcode
import six
from barcode.writer import ImageWriter from barcode.writer import ImageWriter
from escpos.capabilities import get_profile
from escpos.image import EscposImage
from .constants import ( from .constants import (
ESC, BARCODE_FONT_A,
GS, BARCODE_FONT_B,
NUL, BARCODE_FORMATS,
QR_ECLEVEL_L,
QR_ECLEVEL_M,
QR_ECLEVEL_H,
QR_ECLEVEL_Q,
SHEET_ROLL_MODE,
SHEET_SLIP_MODE,
SLIP_PRINT_AND_EJECT,
SLIP_SELECT,
SLIP_EJECT,
)
from .constants import (
QR_MODEL_1,
QR_MODEL_2,
QR_MICRO,
BARCODE_TYPES,
BARCODE_HEIGHT, BARCODE_HEIGHT,
BARCODE_WIDTH,
)
from .constants import BARCODE_FONT_A, BARCODE_FONT_B, BARCODE_FORMATS
from .constants import (
BARCODE_TXT_OFF,
BARCODE_TXT_BTH,
BARCODE_TXT_ABV, BARCODE_TXT_ABV,
BARCODE_TXT_BLW, BARCODE_TXT_BLW,
) BARCODE_TXT_BTH,
from .constants import TXT_SIZE, TXT_NORMAL BARCODE_TXT_OFF,
from .constants import SET_FONT BARCODE_TYPES,
from .constants import LINESPACING_FUNCS, LINESPACING_RESET BARCODE_WIDTH,
from .constants import LINE_DISPLAY_OPEN, LINE_DISPLAY_CLEAR, LINE_DISPLAY_CLOSE
from .constants import (
CD_KICK_DEC_SEQUENCE,
CD_KICK_5,
CD_KICK_2,
PAPER_FULL_CUT,
PAPER_PART_CUT,
BUZZER, BUZZER,
) CD_KICK_2,
from .constants import HW_RESET, HW_SELECT, HW_INIT CD_KICK_5,
from .constants import ( CD_KICK_DEC_SEQUENCE,
CTL_VT,
CTL_CR, CTL_CR,
CTL_FF, CTL_FF,
CTL_LF, CTL_LF,
CTL_SET_HT, CTL_SET_HT,
CTL_VT,
ESC,
GS,
HW_INIT,
HW_RESET,
HW_SELECT,
LINE_DISPLAY_CLEAR,
LINE_DISPLAY_CLOSE,
LINE_DISPLAY_OPEN,
LINESPACING_FUNCS,
LINESPACING_RESET,
NUL,
PANEL_BUTTON_OFF, PANEL_BUTTON_OFF,
PANEL_BUTTON_ON, PANEL_BUTTON_ON,
PAPER_FULL_CUT,
PAPER_PART_CUT,
QR_ECLEVEL_H,
QR_ECLEVEL_L,
QR_ECLEVEL_M,
QR_ECLEVEL_Q,
QR_MICRO,
QR_MODEL_1,
QR_MODEL_2,
RT_MASK_LOWPAPER,
RT_MASK_NOPAPER,
RT_MASK_ONLINE,
RT_MASK_PAPER,
RT_STATUS_ONLINE,
RT_STATUS_PAPER,
SET_FONT,
SHEET_ROLL_MODE,
SHEET_SLIP_MODE,
SLIP_EJECT,
SLIP_PRINT_AND_EJECT,
SLIP_SELECT,
TXT_NORMAL,
TXT_SIZE,
TXT_STYLE,
)
from .exceptions import (
BarcodeCodeError,
BarcodeSizeError,
BarcodeTypeError,
CashDrawerError,
ImageWidthError,
SetVariableError,
TabPosError,
) )
from .constants import TXT_STYLE
from .constants import RT_STATUS_ONLINE, RT_MASK_ONLINE
from .constants import RT_STATUS_PAPER, RT_MASK_PAPER, RT_MASK_LOWPAPER, RT_MASK_NOPAPER
from .exceptions import BarcodeTypeError, BarcodeSizeError, TabPosError
from .exceptions import CashDrawerError, SetVariableError, BarcodeCodeError
from .exceptions import ImageWidthError
from .magicencode import MagicEncode from .magicencode import MagicEncode
from abc import ABCMeta, abstractmethod # abstract base class support
from escpos.image import EscposImage
from escpos.capabilities import get_profile
# Remove special characters and whitespaces of the supported barcode names, # Remove special characters and whitespaces of the supported barcode names,
# convert to uppercase and map them to their original names. # convert to uppercase and map them to their original names.
HW_BARCODE_NAMES = { HW_BARCODE_NAMES = {
@ -100,7 +108,7 @@ SW_BARCODE_NAMES = {
@six.add_metaclass(ABCMeta) @six.add_metaclass(ABCMeta)
class Escpos(object): 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
class. class.
@ -109,19 +117,20 @@ class Escpos(object):
device = None device = None
def __init__(self, profile=None, magic_encode_args=None, **kwargs): def __init__(self, profile=None, magic_encode_args=None, **kwargs):
"""Initialize ESCPOS Printer """Initialize ESCPOS Printer.
:param profile: Printer profile""" :param profile: Printer profile
"""
self.profile = get_profile(profile) self.profile = get_profile(profile)
self.magic = MagicEncode(self, **(magic_encode_args or {})) self.magic = MagicEncode(self, **(magic_encode_args or {}))
def __del__(self): def __del__(self):
"""call self.close upon deletion""" """Call self.close upon deletion."""
self.close() self.close()
@abstractmethod @abstractmethod
def _raw(self, msg): def _raw(self, msg: bytes):
"""Sends raw data to the printer """Send raw data to the printer.
This function has to be individually implemented by the implementations. This function has to be individually implemented by the implementations.
@ -131,7 +140,9 @@ class Escpos(object):
pass pass
def _read(self): def _read(self):
"""Returns a NotImplementedError if the instance of the class doesn't override this method. """Read from printer.
Returns a NotImplementedError if the instance of the class doesn't override this method.
:raises NotImplementedError :raises NotImplementedError
""" """
raise NotImplementedError() raise NotImplementedError()
@ -145,7 +156,7 @@ class Escpos(object):
fragment_height=960, fragment_height=960,
center=False, center=False,
): ):
"""Print an image """Print an image.
You can select whether the printer should print in high density or not. The default value is high density. You can select whether the printer should print in high density or not. The default value is high density.
When printing in low density, the image will be stretched. When printing in low density, the image will be stretched.
@ -252,8 +263,7 @@ class Escpos(object):
self._raw(b"".join(outp)) self._raw(b"".join(outp))
def _image_send_graphics_data(self, m, fn, data): def _image_send_graphics_data(self, m, fn, data):
""" """Calculate and send correct data length for `GS ( L`.
Wrapper for GS ( L, to calculate and send correct data length.
:param m: Modifier//variant for function. Usually '0' :param m: Modifier//variant for function. Usually '0'
:param fn: Function number to use, as byte :param fn: Function number to use, as byte
@ -272,7 +282,7 @@ class Escpos(object):
center=False, center=False,
impl="bitImageRaster", impl="bitImageRaster",
): ):
"""Print QR Code for the provided string """Print QR Code for the provided string.
:param content: The content of the code. Numeric data will be more efficiently compacted. :param content: The content of the code. Numeric data will be more efficiently compacted.
:param ec: Error-correction level to use. One of QR_ECLEVEL_L (default), QR_ECLEVEL_M, QR_ECLEVEL_Q or :param ec: Error-correction level to use. One of QR_ECLEVEL_L (default), QR_ECLEVEL_M, QR_ECLEVEL_Q or
@ -345,7 +355,7 @@ class Escpos(object):
self._send_2d_code_data(six.int2byte(81), cn, b"", b"0") self._send_2d_code_data(six.int2byte(81), cn, b"", b"0")
def _send_2d_code_data(self, fn, cn, data, m=b""): def _send_2d_code_data(self, fn, cn, data, m=b""):
"""Wrapper for GS ( k, to calculate and send correct data length. """Calculate and send correct data length for`GS ( k`.
:param fn: Function to use. :param fn: Function to use.
:param cn: Output code type. Affects available data. :param cn: Output code type. Affects available data.
@ -379,8 +389,8 @@ class Escpos(object):
inp_number //= 256 inp_number //= 256
return outp return outp
def charcode(self, code="AUTO"): def charcode(self, code: str = "AUTO"):
"""Set Character Code Table """Set Character Code Table.
Sets the control sequence from ``CHARCODE`` in :py:mod:`escpos.constants` as active. It will be sent with Sets the control sequence from ``CHARCODE`` in :py:mod:`escpos.constants` as active. It will be sent with
the next text sequence. If you set the variable code to ``AUTO`` it will try to automatically guess the the next text sequence. If you set the variable code to ``AUTO`` it will try to automatically guess the
@ -396,7 +406,8 @@ class Escpos(object):
@staticmethod @staticmethod
def check_barcode(bc, code): def check_barcode(bc, code):
""" """Check if barcode is OK.
This method checks if the barcode is in the proper format. This method checks if the barcode is in the proper format.
The validation concerns the barcode length and the set of characters, but won't compute/validate any checksum. The validation concerns the barcode length and the set of characters, but won't compute/validate any checksum.
The full set of requirement for each barcode type is available in the ESC/POS documentation. The full set of requirement for each barcode type is available in the ESC/POS documentation.
@ -444,15 +455,15 @@ class Escpos(object):
self, self,
code, code,
bc, bc,
height=64, height: int = 64,
width=3, width: int = 3,
pos="BELOW", pos: str = "BELOW",
font="A", font: str = "A",
align_ct=True, align_ct: bool = True,
function_type=None, function_type=None,
check=True, check: bool = True,
force_software=False, force_software: Union[bool, str] = False,
): ) -> None:
"""Print barcode. """Print barcode.
Automatic hardware|software barcode renderer according to the printer capabilities. Automatic hardware|software barcode renderer according to the printer capabilities.
@ -535,9 +546,11 @@ class Escpos(object):
raise BarcodeTypeError(f"Not supported or wrong barcode name {bc}.") raise BarcodeTypeError(f"Not supported or wrong barcode name {bc}.")
if force_software or not capable["hw"] or not capable_bc["hw"]: if force_software or not capable["hw"] or not capable_bc["hw"]:
# based on earlier checks, we require that software mode is not None
assert capable["sw"] is not None
# Select the best possible capable render mode # Select the best possible capable render mode
impl = capable["sw"][0] impl = capable["sw"][0]
if force_software in capable["sw"]: if force_software in capable["sw"] and isinstance(force_software, str):
# Force to a specific mode # Force to a specific mode
impl = force_software impl = force_software
print(f"Using {impl} software barcode renderer") print(f"Using {impl} software barcode renderer")
@ -567,15 +580,15 @@ class Escpos(object):
self, self,
code, code,
bc, bc,
height=64, height: int = 64,
width=3, width: int = 3,
pos="BELOW", pos: str = "BELOW",
font="A", font: str = "A",
align_ct=True, align_ct: bool = True,
function_type=None, function_type=None,
check=True, check: bool = True,
): ):
"""Print Barcode """Print Barcode.
This method allows to print barcodes. The rendering of the barcode is done by the printer and therefore has to This method allows to print barcodes. The rendering of the barcode is done by the printer and therefore has to
be supported by the unit. By default, this method will check whether your barcode text is correct, that is be supported by the unit. By default, this method will check whether your barcode text is correct, that is
@ -726,14 +739,14 @@ class Escpos(object):
self, self,
barcode_type, barcode_type,
data, data,
impl="bitImageColumn", impl: str = "bitImageColumn",
module_height=5, module_height: Union[int, float] = 5,
module_width=0.2, module_width: Union[int, float] = 0.2,
text_distance=5, text_distance: Union[int, float] = 5,
font_size=10, font_size: int = 10,
center=True, center: bool = True,
): ):
"""Print Barcode """Print Barcode.
This method allows to print barcodes. The rendering of the barcode is done by This method allows to print barcodes. The rendering of the barcode is done by
the `barcode` library and sent to the printer as image through one of the the `barcode` library and sent to the printer as image through one of the
@ -773,13 +786,13 @@ class Escpos(object):
* bitImageRaster * bitImageRaster
:param module_height: barcode module height (in mm). :param module_height: barcode module height (in mm).
:type module_height: int | float :type module_height: Union[int, float]
:param module_width: barcode module width (in mm). :param module_width: barcode module width (in mm).
:type module_width: int | float :type module_width: Union[int, float]
:param text_distance: distance from the barcode to the code text (in mm). :param text_distance: distance from the barcode to the code text (in mm).
:type text_distance: int | float :type text_distance: Union[int, float]
:param font_size: font size of the code text (in dots). :param font_size: font size of the code text (in dots).
:type font_size: int :type font_size: int
@ -817,7 +830,7 @@ class Escpos(object):
self.image(image, impl=impl, center=center) self.image(image, impl=impl, center=center)
def text(self, txt): def text(self, txt):
"""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.
The input text has to be encoded in unicode. The input text has to be encoded in unicode.
@ -829,7 +842,7 @@ class Escpos(object):
self.magic.write(txt) self.magic.write(txt)
def textln(self, txt=""): def textln(self, txt=""):
"""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.
The input text has to be encoded in unicode. The input text has to be encoded in unicode.
@ -840,7 +853,7 @@ class Escpos(object):
self.text("{}\n".format(txt)) self.text("{}\n".format(txt))
def ln(self, count=1): def ln(self, count=1):
"""Print a newline or more """Print a newline or more.
:param count: number of newlines to print :param count: number of newlines to print
:raises: :py:exc:`ValueError` if count < 0 :raises: :py:exc:`ValueError` if count < 0
@ -851,7 +864,7 @@ class Escpos(object):
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):
"""Text is printed wrapped to specified columns """Print text wrapped to specifiec columns.
Text has to be encoded in unicode. Text has to be encoded in unicode.
@ -879,7 +892,7 @@ class Escpos(object):
double_height=False, double_height=False,
custom_size=False, custom_size=False,
): ):
"""Set text properties by sending them to the printer """Set text properties by sending them to the printer.
:param align: horizontal position for text, possible values are: :param align: horizontal position for text, possible values are:
@ -918,7 +931,6 @@ class Escpos(object):
:type height: int :type height: int
:type density: int :type density: int
""" """
if custom_size: if custom_size:
if ( if (
1 <= width <= 8 1 <= width <= 8
@ -996,7 +1008,6 @@ class Escpos(object):
:param feed: print and feed before cutting. default: true :param feed: print and feed before cutting. default: true
:raises ValueError: if mode not in ('FULL', 'PART') :raises ValueError: if mode not in ('FULL', 'PART')
""" """
if not feed: if not feed:
self._raw(GS + b"V" + six.int2byte(66) + b"\x00") self._raw(GS + b"V" + six.int2byte(66) + b"\x00")
return return
@ -1019,7 +1030,7 @@ class Escpos(object):
self._raw(PAPER_PART_CUT) self._raw(PAPER_PART_CUT)
def cashdraw(self, pin): def cashdraw(self, pin):
"""Send pulse to kick the cash drawer """Send pulse to kick the cash drawer.
Kick cash drawer on pin 2 (:py:const:`~escpos.constants.CD_KICK_2`) Kick cash drawer on pin 2 (:py:const:`~escpos.constants.CD_KICK_2`)
or pin 5 (:py:const:`~escpos.constants.CD_KICK_5`) or pin 5 (:py:const:`~escpos.constants.CD_KICK_5`)
@ -1041,7 +1052,7 @@ class Escpos(object):
raise CashDrawerError(err) raise CashDrawerError(err)
def linedisplay_select(self, select_display=False): def linedisplay_select(self, select_display=False):
"""Selects the line display or the printer """Select the line display or the printer.
This method is used for line displays that are daisy-chained between your computer and printer. This method is used for line displays that are daisy-chained between your computer and printer.
If you set `select_display` to true, only the display is selected and if you set it to false, If you set `select_display` to true, only the display is selected and if you set it to false,
@ -1056,15 +1067,14 @@ class Escpos(object):
self._raw(LINE_DISPLAY_CLOSE) self._raw(LINE_DISPLAY_CLOSE)
def linedisplay_clear(self): def linedisplay_clear(self):
"""Clears the line display and resets the cursor """Clear the line display and resets the .
This method is used for line displays that are daisy-chained between your computer and printer. This method is used for line displays that are daisy-chained between your computer and printer.
""" """
self._raw(LINE_DISPLAY_CLEAR) self._raw(LINE_DISPLAY_CLEAR)
def linedisplay(self, text): def linedisplay(self, text):
""" """Display text on a line display connected to your printer.
Display text on a line display connected to your printer
You should connect a line display to your printer. You can do this by daisy-chaining You should connect a line display to your printer. You can do this by daisy-chaining
the display between your computer and printer. the display between your computer and printer.
@ -1077,7 +1087,7 @@ class Escpos(object):
self.linedisplay_select(select_display=False) self.linedisplay_select(select_display=False)
def hw(self, hw): def hw(self, hw):
"""Hardware operations """Hardware operations.
:param hw: hardware action, may be: :param hw: hardware action, may be:
@ -1095,7 +1105,7 @@ class Escpos(object):
pass pass
def print_and_feed(self, n=1): def print_and_feed(self, n=1):
"""Print data in print buffer and feed *n* lines """Print data in print buffer and feed *n* lines.
If n not in range (0, 255) then a ValueError will be raised. If n not in range (0, 255) then a ValueError will be raised.
@ -1109,7 +1119,7 @@ class Escpos(object):
raise ValueError("n must be betwen 0 and 255") raise ValueError("n must be betwen 0 and 255")
def control(self, ctl, count=5, tab_size=8): def control(self, ctl, count=5, tab_size=8):
"""Feed control sequences """Feed control sequences.
:param ctl: string for the following control sequences: :param ctl: string for the following control sequences:
@ -1145,7 +1155,7 @@ class Escpos(object):
self._raw(CTL_VT) self._raw(CTL_VT)
def panel_buttons(self, enable=True): def panel_buttons(self, enable=True):
"""Controls the panel buttons on the printer (e.g. FEED) """Control the panel buttons on the printer (e.g. FEED).
When enable is set to False the panel buttons on the printer When enable is set to False the panel buttons on the printer
will be disabled. will be disabled.
@ -1175,9 +1185,9 @@ class Escpos(object):
self._raw(PANEL_BUTTON_OFF) self._raw(PANEL_BUTTON_OFF)
def query_status(self, mode): def query_status(self, mode):
""" """Query the printer for its status.
Queries the printer for its status, and returns an array
of integers containing it. Returns an array of integers 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.
@ -1189,8 +1199,7 @@ class Escpos(object):
return status return status
def is_online(self): def is_online(self):
""" """Query the online status of the printer.
Queries the online status of the printer.
:returns: When online, returns ``True``; ``False`` otherwise. :returns: When online, returns ``True``; ``False`` otherwise.
:rtype: bool :rtype: bool
@ -1201,8 +1210,7 @@ class Escpos(object):
return not (status[0] & RT_MASK_ONLINE) return not (status[0] & RT_MASK_ONLINE)
def paper_status(self): def paper_status(self):
""" """Query the paper status of the printer.
Queries 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
the near-end sensor and 0 if there is no paper. the near-end sensor and 0 if there is no paper.
@ -1221,7 +1229,7 @@ class Escpos(object):
return 2 return 2
def target(self, type="ROLL"): def target(self, type="ROLL"):
"""Select where to print to """Select where to print to.
Print to the thermal printer by default (ROLL) or Print to the thermal printer by default (ROLL) or
print to the slip dot matrix printer if supported (SLIP) print to the slip dot matrix printer if supported (SLIP)
@ -1234,11 +1242,11 @@ class Escpos(object):
raise ValueError("Unsupported target") raise ValueError("Unsupported target")
def eject_slip(self): def eject_slip(self):
"""Eject the slip/cheque""" """Eject the slip/cheque."""
self._raw(SLIP_EJECT) self._raw(SLIP_EJECT)
def print_and_eject_slip(self): def print_and_eject_slip(self):
"""Print and eject """Print and eject.
Prints data from the buffer to the slip station and if the paper Prints data from the buffer to the slip station and if the paper
sensor is covered, reverses the slip out the front of the printer sensor is covered, reverses the slip out the front of the printer
@ -1248,7 +1256,7 @@ class Escpos(object):
self._raw(SLIP_PRINT_AND_EJECT) self._raw(SLIP_PRINT_AND_EJECT)
def use_slip_only(self): def use_slip_only(self):
"""Selects the Slip Station for all functions. """Select the Slip Station for all functions.
The receipt station is the default setting after the printer The receipt station is the default setting after the printer
is initialized or the Clear Printer (0x10) command is received is initialized or the Clear Printer (0x10) command is received
@ -1256,7 +1264,7 @@ class Escpos(object):
self._raw(SLIP_SELECT) self._raw(SLIP_SELECT)
def buzzer(self, times=2, duration=4): def buzzer(self, times=2, duration=4):
"""Activate the internal printer buzzer (only supported printers). """Activate the internal printer buzzer on supported printers.
The 'times' parameter refers to the 'n' escpos command parameter, The 'times' parameter refers to the 'n' escpos command parameter,
which means how many times the buzzer will be 'beeped'. which means how many times the buzzer will be 'beeped'.
@ -1265,7 +1273,6 @@ class Escpos(object):
:param duration: Integer between 1 and 9, indicates the beep duration. :param duration: Integer between 1 and 9, indicates the beep duration.
:returns: None :returns: None
""" """
if not 1 <= times <= 9: if not 1 <= times <= 9:
raise ValueError("times must be between 1 and 9") raise ValueError("times must be between 1 and 9")
if not 1 <= duration <= 9: if not 1 <= duration <= 9:
@ -1275,7 +1282,7 @@ class Escpos(object):
class EscposIO(object): class EscposIO(object):
"""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
and assign it to the EscposIO class. and assign it to the EscposIO class.
@ -1295,7 +1302,8 @@ class EscposIO(object):
""" """
def __init__(self, printer, autocut=True, autoclose=True, **kwargs): def __init__(self, printer, autocut=True, autoclose=True, **kwargs):
""" """Initialize object.
:param printer: An EscPos-printer object :param printer: An EscPos-printer object
:type printer: escpos.Escpos :type printer: escpos.Escpos
:param autocut: If True, paper is automatically cut after the `with`-statement *default*: True :param autocut: If True, paper is automatically cut after the `with`-statement *default*: True
@ -1307,7 +1315,7 @@ class EscposIO(object):
self.autoclose = autoclose self.autoclose = autoclose
def set(self, **kwargs): def set(self, **kwargs):
"""Set the printer-parameters """Set the printer-parameters.
Controls which parameters will be passed to :py:meth:`Escpos.set() <escpos.escpos.Escpos.set()>`. Controls which parameters will be passed to :py:meth:`Escpos.set() <escpos.escpos.Escpos.set()>`.
For more information on the parameters see the :py:meth:`set() <escpos.escpos.Escpos.set()>`-methods For more information on the parameters see the :py:meth:`set() <escpos.escpos.Escpos.set()>`-methods
@ -1319,6 +1327,7 @@ class EscposIO(object):
self.params.update(kwargs) self.params.update(kwargs)
def writelines(self, text, **kwargs): def writelines(self, text, **kwargs):
"""Print text."""
params = dict(self.params) params = dict(self.params)
params.update(kwargs) params.update(kwargs)
@ -1341,14 +1350,18 @@ class EscposIO(object):
self.printer.text("{0}\n".format(line)) self.printer.text("{0}\n".format(line))
def close(self): def close(self):
"""called upon closing the `with`-statement""" """Close printer.
Called upon closing the `with`-statement.
"""
self.printer.close() self.printer.close()
def __enter__(self, **kwargs): def __enter__(self, **kwargs):
"""Enter context."""
return self return self
def __exit__(self, type, value, traceback): def __exit__(self, type, value, traceback):
""" """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),
then :py:meth:`printer.cut() <escpos.escpos.Escpos.cut()>` will be called here. then :py:meth:`printer.cut() <escpos.escpos.Escpos.cut()>` will be called here.

View File

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" ESC/POS Exceptions classes """ESC/POS Exceptions classes.
Result/Exit codes: Result/Exit codes:
@ -27,9 +27,10 @@ Result/Exit codes:
class Error(Exception): class Error(Exception):
"""Base class for ESC/POS errors""" """Base class for ESC/POS errors."""
def __init__(self, msg, status=None): def __init__(self, msg, status=None):
"""Initialize Error object."""
Exception.__init__(self) Exception.__init__(self)
self.msg = msg self.msg = msg
self.resultcode = 1 self.resultcode = 1
@ -37,6 +38,7 @@ class Error(Exception):
self.resultcode = status self.resultcode = status
def __str__(self): def __str__(self):
"""Return string representation of Error."""
return self.msg return self.msg
@ -49,11 +51,13 @@ class BarcodeTypeError(Error):
""" """
def __init__(self, msg=""): def __init__(self, msg=""):
"""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):
"""Return string representation of BarcodeTypeError."""
return "No Barcode type is defined ({msg})".format(msg=self.msg) return "No Barcode type is defined ({msg})".format(msg=self.msg)
@ -66,11 +70,13 @@ class BarcodeSizeError(Error):
""" """
def __init__(self, msg=""): def __init__(self, msg=""):
"""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):
"""Return string representation of BarcodeSizeError."""
return "Barcode size is out of range ({msg})".format(msg=self.msg) return "Barcode size is out of range ({msg})".format(msg=self.msg)
@ -83,11 +89,13 @@ class BarcodeCodeError(Error):
""" """
def __init__(self, msg=""): def __init__(self, msg=""):
"""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):
"""Return string representation of BarcodeCodeError."""
return "No Barcode code was supplied ({msg})".format(msg=self.msg) return "No Barcode code was supplied ({msg})".format(msg=self.msg)
@ -98,11 +106,13 @@ class ImageSizeError(Error):
""" """
def __init__(self, msg=""): def __init__(self, msg=""):
"""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):
"""Return string representation of ImageSizeError."""
return "Image height is longer than 255px and can't be printed ({msg})".format( return "Image height is longer than 255px and can't be printed ({msg})".format(
msg=self.msg msg=self.msg
) )
@ -115,11 +125,13 @@ class ImageWidthError(Error):
""" """
def __init__(self, msg=""): def __init__(self, msg=""):
"""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):
"""Return string representation of ImageWidthError."""
return "Image width is too large ({msg})".format(msg=self.msg) return "Image width is too large ({msg})".format(msg=self.msg)
@ -131,11 +143,13 @@ class TextError(Error):
""" """
def __init__(self, msg=""): def __init__(self, msg=""):
"""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):
"""Return string representation of TextError."""
return "Text string must be supplied to the text() method ({msg})".format( return "Text string must be supplied to the text() method ({msg})".format(
msg=self.msg msg=self.msg
) )
@ -149,16 +163,20 @@ class CashDrawerError(Error):
""" """
def __init__(self, msg=""): def __init__(self, msg=""):
"""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):
"""Return string representation of CashDrawerError."""
return "Valid pin must be set to send pulse ({msg})".format(msg=self.msg) return "Valid pin must be set to send pulse ({msg})".format(msg=self.msg)
class TabPosError(Error): class TabPosError(Error):
"""Valid tab positions must be set by using from 1 to 32 tabs, and between 1 and 255 tab size values. """Tab position is invalid.
Valid tab positions must be set by using from 1 to 32 tabs, and between 1 and 255 tab size values.
Both values multiplied must not exceed 255, since it is the maximum tab value. Both values multiplied must not exceed 255, since it is the maximum tab value.
This exception is raised by :py:meth:`escpos.escpos.Escpos.control`. This exception is raised by :py:meth:`escpos.escpos.Escpos.control`.
@ -166,11 +184,13 @@ class TabPosError(Error):
""" """
def __init__(self, msg=""): def __init__(self, msg=""):
"""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):
"""Return string representation of TabPosError."""
return "Valid tab positions must be in the range 0 to 16 ({msg})".format( return "Valid tab positions must be in the range 0 to 16 ({msg})".format(
msg=self.msg msg=self.msg
) )
@ -184,43 +204,49 @@ class CharCodeError(Error):
""" """
def __init__(self, msg=""): def __init__(self, msg=""):
"""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):
"""Return string representation of CharCodeError."""
return "Valid char code must be set ({msg})".format(msg=self.msg) return "Valid char code must be set ({msg})".format(msg=self.msg)
class USBNotFoundError(Error): class USBNotFoundError(Error):
"""Device wasn't found (probably not plugged in) """Device wasn't found (probably not plugged in).
The USB device seems to be not plugged in. The USB device seems to be not plugged in.
Ths returncode for this exception is `90`. Ths returncode for this exception is `90`.
""" """
def __init__(self, msg=""): def __init__(self, msg=""):
"""Initialize USBNotFoundError 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):
"""Return string representation of USBNotFoundError."""
return "USB device not found ({msg})".format(msg=self.msg) return "USB device not found ({msg})".format(msg=self.msg)
class SetVariableError(Error): class SetVariableError(Error):
"""A set method variable was out of range """A set method variable was out of range.
Check set variables against minimum and maximum values Check set variables against minimum and maximum values
Ths returncode for this exception is `100`. Ths returncode for this exception is `100`.
""" """
def __init__(self, msg=""): def __init__(self, msg=""):
"""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):
"""Return string representation of SetVariableError."""
return "Set variable out of range ({msg})".format(msg=self.msg) return "Set variable out of range ({msg})".format(msg=self.msg)
@ -228,48 +254,54 @@ class SetVariableError(Error):
class ConfigNotFoundError(Error): class ConfigNotFoundError(Error):
"""The configuration file was not found """The configuration file was not found.
The default or passed configuration file could not be read The default or passed configuration file could not be read
Ths returncode for this exception is `200`. Ths returncode for this exception is `200`.
""" """
def __init__(self, msg=""): def __init__(self, msg=""):
"""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):
"""Return string representation of ConfigNotFoundError."""
return "Configuration not found ({msg})".format(msg=self.msg) return "Configuration not found ({msg})".format(msg=self.msg)
class ConfigSyntaxError(Error): class ConfigSyntaxError(Error):
"""The configuration file is invalid """The configuration file is invalid.
The syntax is incorrect The syntax is incorrect
Ths returncode for this exception is `210`. Ths returncode for this exception is `210`.
""" """
def __init__(self, msg=""): def __init__(self, msg=""):
"""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):
"""Return string representation of ConfigSyntaxError."""
return "Configuration syntax is invalid ({msg})".format(msg=self.msg) return "Configuration syntax is invalid ({msg})".format(msg=self.msg)
class ConfigSectionMissingError(Error): class ConfigSectionMissingError(Error):
"""The configuration file is missing a section """The configuration file is missing a section.
The part of the config asked for doesn't exist in the loaded configuration The part of the config asked for doesn't exist in the loaded configuration
Ths returncode for this exception is `220`. Ths returncode for this exception is `220`.
""" """
def __init__(self, msg=""): def __init__(self, msg=""):
"""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):
"""Return string representation of ConfigSectionMissingError."""
return "Configuration section is missing ({msg})".format(msg=self.msg) return "Configuration section is missing ({msg})".format(msg=self.msg)

View File

@ -1,4 +1,4 @@
""" Image format handling class """Image format handling class.
This module contains the image format handler :py:class:`EscposImage`. This module contains the image format handler :py:class:`EscposImage`.
@ -10,6 +10,7 @@ This module contains the image format handler :py:class:`EscposImage`.
import math import math
from PIL import Image, ImageOps from PIL import Image, ImageOps
@ -22,8 +23,7 @@ class EscposImage(object):
""" """
def __init__(self, img_source): def __init__(self, img_source):
""" """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.
""" """
@ -49,30 +49,23 @@ class EscposImage(object):
@property @property
def width(self): def width(self):
""" """Return width of image in pixels."""
Width of image in pixels
"""
width_pixels, _ = self._im.size width_pixels, _ = self._im.size
return width_pixels return width_pixels
@property @property
def width_bytes(self): def width_bytes(self):
""" """Return width of image if you use 8 pixels per byte and 0-pad at the end."""
Width of image if you use 8 pixels per byte and 0-pad at the end.
"""
return (self.width + 7) >> 3 return (self.width + 7) >> 3
@property @property
def height(self): def height(self):
""" """Height of image in pixels."""
Height of image in pixels
"""
_, height_pixels = self._im.size _, height_pixels = self._im.size
return height_pixels return height_pixels
def to_column_format(self, high_density_vertical=True): def to_column_format(self, high_density_vertical=True):
""" """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
""" """
@ -89,14 +82,11 @@ class EscposImage(object):
left += line_height left += line_height
def to_raster_format(self): def to_raster_format(self):
""" """Convert image to raster-format binary."""
Convert image to raster-format binary
"""
return self._im.tobytes() return self._im.tobytes()
def split(self, fragment_height): def split(self, fragment_height):
""" """Split an image into multiple fragments after fragment_height pixels.
Split an image into multiple fragments after fragment_height pixels
:param fragment_height: height of fragment :param fragment_height: height of fragment
:return: list of PIL objects :return: list of PIL objects
@ -113,7 +103,7 @@ class EscposImage(object):
return fragments return fragments
def center(self, max_width): def center(self, max_width):
"""In-place image centering """Center image in place.
:param: Maximum width in order to deduce x offset for centering :param: Maximum width in order to deduce x offset for centering
:return: None :return: None

View File

@ -1,6 +1,6 @@
#!/usr/bin/python #!/usr/bin/python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" Magic Encode """Magic Encode.
This module tries to convert an UTF-8 string to an encoded string for the printer. This module tries to convert an UTF-8 string to an encoded string for the printer.
It uses trial and error in order to guess the right codepage. It uses trial and error in order to guess the right codepage.
@ -13,17 +13,18 @@ The code is based on the encoding-code in py-xml-escpos by @fvdsn.
""" """
import re
from builtins import bytes from builtins import bytes
import six
from .codepages import CodePages
from .constants import CODEPAGE_CHANGE from .constants import CODEPAGE_CHANGE
from .exceptions import Error from .exceptions import Error
from .codepages import CodePages
import six
import re
class Encoder(object): class Encoder(object):
"""Takes a list of available code spaces. Picks the right one for a """Take available code spaces and pick the right one for a given character.
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
thus already knows what the final byte in the target encoding would thus already knows what the final byte in the target encoding would
@ -39,20 +40,24 @@ class Encoder(object):
""" """
def __init__(self, codepage_map): def __init__(self, codepage_map):
"""Initialize encoder."""
self.codepages = codepage_map self.codepages = codepage_map
self.available_encodings = set(codepage_map.keys()) self.available_encodings = set(codepage_map.keys())
self.available_characters = {} self.available_characters = {}
self.used_encodings = set() self.used_encodings = set()
def get_sequence(self, encoding): def get_sequence(self, encoding):
"""Get a sequence."""
return int(self.codepages[encoding]) return int(self.codepages[encoding])
def get_encoding_name(self, encoding): def get_encoding_name(self, encoding):
"""Given an encoding provided by the user, will return a """Return a canonical encoding name.
Given an encoding provided by the user, will return a
canonical encoding name; and also validate that the encoding canonical encoding name; and also validate that the encoding
is supported. is supported.
TODO: Support encoding aliases: pc437 instead of cp437. .. todo:: Support encoding aliases: pc437 instead of cp437.
""" """
encoding = CodePages.get_encoding_name(encoding) encoding = CodePages.get_encoding_name(encoding)
if encoding not in self.codepages: if encoding not in self.codepages:
@ -66,7 +71,7 @@ class Encoder(object):
@staticmethod @staticmethod
def _get_codepage_char_list(encoding): def _get_codepage_char_list(encoding):
"""Get codepage character list """Get codepage character list.
Gets characters 128-255 for a given code page, as an array. Gets characters 128-255 for a given code page, as an array.
@ -92,7 +97,7 @@ class Encoder(object):
raise LookupError("Can't find a known encoding for {}".format(encoding)) raise LookupError("Can't find a known encoding for {}".format(encoding))
def _get_codepage_char_map(self, encoding): def _get_codepage_char_map(self, encoding):
"""Get codepage character map """Get codepage character map.
Process an encoding and return a map of UTF-characters to code points Process an encoding and return a map of UTF-characters to code points
in this encoding. in this encoding.
@ -130,7 +135,7 @@ class Encoder(object):
@staticmethod @staticmethod
def _encode_char(char, charmap, defaultchar): def _encode_char(char, charmap, defaultchar):
"""Encode a single character with the given encoding map """Encode a single character with the given encoding map.
:param char: char to encode :param char: char to encode
:param charmap: dictionary for mapping characters in this code page :param charmap: dictionary for mapping characters in this code page
@ -142,7 +147,7 @@ class Encoder(object):
return ord(defaultchar) return ord(defaultchar)
def encode(self, text, encoding, defaultchar="?"): def encode(self, text, encoding, defaultchar="?"):
"""Encode text under the given encoding """Encode text under the given encoding.
:param text: Text to encode :param text: Text to encode
:param encoding: Encoding name to use (must be defined in capabilities) :param encoding: Encoding name to use (must be defined in capabilities)
@ -159,7 +164,9 @@ class Encoder(object):
return (key in self.used_encodings, index) return (key in self.used_encodings, index)
def find_suitable_encoding(self, char): def find_suitable_encoding(self, char):
"""The order of our search is a specific one: """Search in a specific order for a suitable encoding.
It is the following order:
1. code pages that we already tried before; there is a good 1. code pages that we already tried before; there is a good
chance they might work again, reducing the search space, chance they might work again, reducing the search space,
@ -184,7 +191,9 @@ class Encoder(object):
def split_writable_text(encoder, text, encoding): def split_writable_text(encoder, text, encoding):
"""Splits off as many characters from the beginning of text as """Split up the writable text.
Splits off as many characters from the beginning of text as
are writable with "encoding". Returns a 2-tuple (writable, rest). are writable with "encoding". Returns a 2-tuple (writable, rest).
""" """
if not encoding: if not encoding:
@ -199,7 +208,9 @@ def split_writable_text(encoder, text, encoding):
class MagicEncode(object): class MagicEncode(object):
"""A helper that helps us to automatically switch to the right """Help switching to the right code page.
A helper that helps us to automatically switch to the right
code page to encode any given Unicode character. code page to encode any given Unicode character.
This will consider the printers supported codepages, according This will consider the printers supported codepages, according
@ -213,7 +224,7 @@ class MagicEncode(object):
def __init__( def __init__(
self, driver, encoding=None, disabled=False, defaultsymbol="?", encoder=None self, driver, encoding=None, disabled=False, defaultsymbol="?", encoder=None
): ):
""" """Initialize magic encode.
:param driver: :param driver:
:param encoding: If you know the current encoding of the printer :param encoding: If you know the current encoding of the printer
@ -235,7 +246,7 @@ class MagicEncode(object):
self.disabled = disabled self.disabled = disabled
def force_encoding(self, encoding): def force_encoding(self, encoding):
"""Sets a fixed encoding. The change is emitted right away. """Set a fixed encoding. The change is emitted right away.
From now one, this buffer will switch the code page anymore. From now one, this buffer will switch the code page anymore.
However, it will still keep track of the current code page. However, it will still keep track of the current code page.
@ -248,7 +259,6 @@ class MagicEncode(object):
def write(self, text): def write(self, text):
"""Write the text, automatically switching encodings.""" """Write the text, automatically switching encodings."""
if self.disabled: if self.disabled:
self.write_with_encoding(self.encoding, text) self.write_with_encoding(self.encoding, text)
return return
@ -277,12 +287,16 @@ class MagicEncode(object):
self.write_with_encoding(encoding, to_write) self.write_with_encoding(encoding, to_write)
def _handle_character_failed(self, char): def _handle_character_failed(self, char):
"""Called when no codepage was found to render a character.""" """Write a default symbol.
Called when no codepage was found to render a character.
"""
# Writing the default symbol via write() allows us to avoid # Writing the default symbol via write() allows us to avoid
# unnecesary codepage switches. # unnecesary codepage switches.
self.write(self.defaultsymbol) self.write(self.defaultsymbol)
def write_with_encoding(self, encoding, text): 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 six.text_type:
raise Error( raise Error(
"The supplied text has to be unicode, but is of type {type}.".format( "The supplied text has to be unicode, but is of type {type}.".format(

View File

@ -1,6 +1,6 @@
#!/usr/bin/python #!/usr/bin/python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" This module contains the implementations of abstract base class :py:class:`Escpos`. """This module contains the implementations of abstract base class :py:class:`Escpos`.
:author: `Manuel F Martinez <manpaz@bashlinux.com>`_ and others :author: `Manuel F Martinez <manpaz@bashlinux.com>`_ and others
:organization: Bashlinux and `python-escpos <https://github.com/python-escpos>`_ :organization: Bashlinux and `python-escpos <https://github.com/python-escpos>`_
@ -31,16 +31,17 @@ except ImportError:
_CUPSPRINT = False _CUPSPRINT = False
try: try:
import cups
import tempfile import tempfile
import cups
_CUPSPRINT = True _CUPSPRINT = True
except ImportError: except ImportError:
pass pass
class Usb(Escpos): class Usb(Escpos):
"""USB printer """USB printer.
This class describes a printer that natively speaks USB. This class describes a printer that natively speaks USB.
@ -62,7 +63,8 @@ class Usb(Escpos):
*args, *args,
**kwargs **kwargs
): # noqa: N803 ): # noqa: N803
""" """Initialize USB printer.
:param idVendor: Vendor ID :param idVendor: Vendor ID
:param idProduct: Product ID :param idProduct: Product ID
:param usb_args: Optional USB arguments (e.g. custom_match) :param usb_args: Optional USB arguments (e.g. custom_match)
@ -122,7 +124,7 @@ class Usb(Escpos):
print("Could not set configuration: {0}".format(str(e))) print("Could not set configuration: {0}".format(str(e)))
def _raw(self, msg): def _raw(self, msg):
"""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 :type msg: bytes
@ -130,18 +132,18 @@ class Usb(Escpos):
self.device.write(self.out_ep, msg, self.timeout) self.device.write(self.out_ep, msg, self.timeout)
def _read(self): def _read(self):
"""Reads a data buffer and returns it to the caller.""" """Read a data buffer and return it to the caller."""
return self.device.read(self.in_ep, 16) return self.device.read(self.in_ep, 16)
def close(self): def close(self):
"""Release USB interface""" """Release USB interface."""
if self.device: if self.device:
usb.util.dispose_resources(self.device) usb.util.dispose_resources(self.device)
self.device = None self.device = None
class Serial(Escpos): class Serial(Escpos):
"""Serial printer """Serial printer.
This class describes a printer that is connected by serial interface. This class describes a printer that is connected by serial interface.
@ -165,7 +167,7 @@ class Serial(Escpos):
*args, *args,
**kwargs **kwargs
): ):
""" """Initialize serial printer.
:param devfile: Device file under dev filesystem :param devfile: Device file under dev filesystem
:param baudrate: Baud rate for serial transmission :param baudrate: Baud rate for serial transmission
@ -189,7 +191,7 @@ class Serial(Escpos):
self.open() self.open()
def open(self): def open(self):
"""Setup serial port and set is as escpos device""" """Set up serial port and set is as escpos device."""
if self.device is not None and self.device.is_open: if self.device is not None and self.device.is_open:
self.close() self.close()
self.device = serial.Serial( self.device = serial.Serial(
@ -209,7 +211,7 @@ class Serial(Escpos):
print("Unable to open serial printer on: {0}".format(str(self.devfile))) print("Unable to open serial printer on: {0}".format(str(self.devfile)))
def _raw(self, msg): def _raw(self, msg):
"""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 :type msg: bytes
@ -217,18 +219,18 @@ class Serial(Escpos):
self.device.write(msg) self.device.write(msg)
def _read(self): def _read(self):
"""Reads a data buffer and returns it to the caller.""" """Read the data buffer and return it to the caller."""
return self.device.read(16) return self.device.read(16)
def close(self): def close(self):
"""Close Serial interface""" """Close Serial interface."""
if self.device is not None and self.device.is_open: if self.device is not None and self.device.is_open:
self.device.flush() self.device.flush()
self.device.close() self.device.close()
class Network(Escpos): class Network(Escpos):
"""Network printer """Network printer.
This class is used to attach to a networked printer. You can also use this in order to attach to a printer that This class is used to attach to a networked printer. You can also use this in order to attach to a printer that
is forwarded with ``socat``. is forwarded with ``socat``.
@ -251,7 +253,7 @@ class Network(Escpos):
""" """
def __init__(self, host, port=9100, timeout=60, *args, **kwargs): def __init__(self, host, port=9100, timeout=60, *args, **kwargs):
""" """Initialize network printer.
:param host: Printer's hostname or IP address :param host: Printer's hostname or IP address
:param port: Port to write to :param port: Port to write to
@ -264,7 +266,7 @@ class Network(Escpos):
self.open() self.open()
def open(self): def open(self):
"""Open TCP socket with ``socket``-library and set it as escpos device""" """Open TCP socket with ``socket``-library and set it as escpos device."""
self.device = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.device = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.device.settimeout(self.timeout) self.device.settimeout(self.timeout)
self.device.connect((self.host, self.port)) self.device.connect((self.host, self.port))
@ -273,7 +275,7 @@ class Network(Escpos):
print("Could not open socket for {0}".format(self.host)) print("Could not open socket for {0}".format(self.host))
def _raw(self, msg): def _raw(self, msg):
"""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 :type msg: bytes
@ -281,12 +283,11 @@ class Network(Escpos):
self.device.sendall(msg) self.device.sendall(msg)
def _read(self): def _read(self):
"""Read data from the TCP socket""" """Read data from the TCP socket."""
return self.device.recv(16) return self.device.recv(16)
def close(self): def close(self):
"""Close TCP connection""" """Close TCP connection."""
if self.device is not None: if self.device is not None:
try: try:
self.device.shutdown(socket.SHUT_RDWR) self.device.shutdown(socket.SHUT_RDWR)
@ -296,7 +297,7 @@ class Network(Escpos):
class File(Escpos): class File(Escpos):
"""Generic file printer """Generic file printer.
This class is used for parallel port printer or other printers that are directly attached to the filesystem. This class is used for parallel port printer or other printers that are directly attached to the filesystem.
Note that you should stay away from using USB-to-Parallel-Adapter since they are unreliable Note that you should stay away from using USB-to-Parallel-Adapter since they are unreliable
@ -310,7 +311,7 @@ class File(Escpos):
""" """
def __init__(self, devfile="/dev/usb/lp0", auto_flush=True, *args, **kwargs): def __init__(self, devfile="/dev/usb/lp0", auto_flush=True, *args, **kwargs):
""" """Initialize file printer with device file.
:param devfile: Device file under dev filesystem :param devfile: Device file under dev filesystem
:param auto_flush: automatically call flush after every call of _raw() :param auto_flush: automatically call flush after every call of _raw()
@ -321,18 +322,18 @@ class File(Escpos):
self.open() self.open()
def open(self): def open(self):
"""Open system file""" """Open system file."""
self.device = open(self.devfile, "wb") self.device = open(self.devfile, "wb")
if self.device is None: if self.device is None:
print("Could not open the specified file {0}".format(self.devfile)) print("Could not open the specified file {0}".format(self.devfile))
def flush(self): def flush(self):
"""Flush printing content""" """Flush printing content."""
self.device.flush() self.device.flush()
def _raw(self, msg): def _raw(self, msg):
"""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 :type msg: bytes
@ -342,14 +343,14 @@ class File(Escpos):
self.flush() self.flush()
def close(self): def close(self):
"""Close system file""" """Close system file."""
if self.device is not None: if self.device is not None:
self.device.flush() self.device.flush()
self.device.close() self.device.close()
class Dummy(Escpos): class Dummy(Escpos):
"""Dummy printer """Dummy printer.
This class is used for saving commands to a variable, for use in situations where This class is used for saving commands to a variable, for use in situations where
there is no need to send commands to an actual printer. This includes there is no need to send commands to an actual printer. This includes
@ -363,12 +364,12 @@ class Dummy(Escpos):
""" """
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
""" """ """Init with empty output list."""
Escpos.__init__(self, *args, **kwargs) Escpos.__init__(self, *args, **kwargs)
self._output_list = [] self._output_list = []
def _raw(self, msg): def _raw(self, msg):
"""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 :type msg: bytes
@ -377,11 +378,11 @@ class Dummy(Escpos):
@property @property
def output(self): def output(self):
"""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):
"""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
and want to use the Dummy printer for new output. and want to use the Dummy printer for new output.
@ -389,13 +390,20 @@ class Dummy(Escpos):
del self._output_list[:] del self._output_list[:]
def close(self): def close(self):
"""Close not implemented for Dummy printer."""
pass pass
if _WIN32PRINT: if _WIN32PRINT:
class Win32Raw(Escpos): class Win32Raw(Escpos):
"""Printer binding for win32 API.
Uses the module pywin32 for printing.
"""
def __init__(self, printer_name=None, *args, **kwargs): def __init__(self, printer_name=None, *args, **kwargs):
"""Initialize default printer."""
Escpos.__init__(self, *args, **kwargs) Escpos.__init__(self, *args, **kwargs)
if printer_name is not None: if printer_name is not None:
self.printer_name = printer_name self.printer_name = printer_name
@ -405,6 +413,7 @@ if _WIN32PRINT:
self.open() self.open()
def open(self, job_name="python-escpos"): def open(self, job_name="python-escpos"):
"""Open connection to default printer."""
if self.printer_name is None: if self.printer_name is None:
raise Exception("Printer not found") raise Exception("Printer not found")
self.hPrinter = win32print.OpenPrinter(self.printer_name) self.hPrinter = win32print.OpenPrinter(self.printer_name)
@ -414,6 +423,7 @@ if _WIN32PRINT:
win32print.StartPagePrinter(self.hPrinter) win32print.StartPagePrinter(self.hPrinter)
def close(self): def close(self):
"""Close connection to default printer."""
if not self.hPrinter: if not self.hPrinter:
return return
win32print.EndPagePrinter(self.hPrinter) win32print.EndPagePrinter(self.hPrinter)
@ -422,7 +432,7 @@ if _WIN32PRINT:
self.hPrinter = None self.hPrinter = None
def _raw(self, msg): def _raw(self, msg):
"""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 :type msg: bytes
@ -448,7 +458,7 @@ if _CUPSPRINT:
""" """
def __init__(self, printer_name=None, *args, **kwargs): def __init__(self, printer_name=None, *args, **kwargs):
"""CupsPrinter class constructor. """Class constructor for CupsPrinter.
:param printer_name: CUPS printer name (Optional) :param printer_name: CUPS printer name (Optional)
:type printer_name: str :type printer_name: str
@ -477,7 +487,7 @@ if _CUPSPRINT:
return self.conn.getPrinters() return self.conn.getPrinters()
def open(self, job_name="python-escpos"): def open(self, job_name="python-escpos"):
"""Setup a new print job and target printer. """Set up a new print job and target the printer.
A call to this method is required to send new jobs to A call to this method is required to send new jobs to
the same CUPS connection. the same CUPS connection.
@ -491,7 +501,7 @@ if _CUPSPRINT:
self.tmpfile = tempfile.NamedTemporaryFile(delete=True) self.tmpfile = tempfile.NamedTemporaryFile(delete=True)
def _raw(self, msg): def _raw(self, msg):
"""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 :type msg: bytes
@ -582,7 +592,7 @@ if not sys.platform.startswith("win"):
self.lp.terminate() self.lp.terminate()
def flush(self): def flush(self):
"""End line and wait for new commands""" """End line and wait for new commands."""
if self.lp.stdin.writable(): if self.lp.stdin.writable():
self.lp.stdin.write(b"\n") self.lp.stdin.write(b"\n")
if self.lp.stdin.closed is False: if self.lp.stdin.closed is False:

View File

@ -1,4 +1,5 @@
import pytest import pytest
from escpos.printer import Dummy from escpos.printer import Dummy

View File

@ -1,16 +1,18 @@
#!/usr/bin/python #!/usr/bin/python
"""verifies that the metaclass abc is properly used by ESC/POS """verifies that the metaclass abc is properly used by ESC/POS
:author: `Patrick Kanzler <patrick.kanzler@fablab.fau.de>`_ :author: `Patrick Kanzler <dev@pkanzler.de>`_
:organization: `python-escpos <https://github.com/python-escpos>`_ :organization: `python-escpos <https://github.com/python-escpos>`_
:copyright: Copyright (c) 2016 Patrick Kanzler :copyright: Copyright (c) 2016 Patrick Kanzler
:license: MIT :license: MIT
""" """
import pytest
import escpos.escpos as escpos
from abc import ABCMeta from abc import ABCMeta
import pytest
import escpos.escpos as escpos
def test_abstract_base_class_raises(): def test_abstract_base_class_raises():
"""test whether the abstract base class raises an exception for ESC/POS""" """test whether the abstract base class raises an exception for ESC/POS"""

View File

@ -4,10 +4,12 @@
import os import os
import shutil
import tempfile
import pytest import pytest
from scripttest import TestFileEnvironment as TFE from scripttest import TestFileEnvironment as TFE
import tempfile
import shutil
import escpos import escpos
TEST_DIR = tempfile.mkdtemp() + "/cli-test" TEST_DIR = tempfile.mkdtemp() + "/cli-test"

View File

@ -1,10 +1,11 @@
#!/usr/bin/python #!/usr/bin/python
import escpos.printer as printer
from escpos.capabilities import Profile, BARCODE_B
from escpos.exceptions import BarcodeTypeError, BarcodeCodeError
import pytest import pytest
import escpos.printer as printer
from escpos.capabilities import BARCODE_B, Profile
from escpos.exceptions import BarcodeCodeError, BarcodeTypeError
@pytest.mark.parametrize( @pytest.mark.parametrize(
"bctype,data,expected", "bctype,data,expected",

View File

@ -1,5 +1,5 @@
import six
import pytest import pytest
import six
from escpos import printer from escpos import printer
from escpos.constants import BUZZER from escpos.constants import BUZZER

View File

@ -1,8 +1,9 @@
#!/usr/bin/python #!/usr/bin/python
import pytest
import escpos.printer as printer import escpos.printer as printer
from escpos.exceptions import CashDrawerError from escpos.exceptions import CashDrawerError
import pytest
def test_raise_CashDrawerError(): def test_raise_CashDrawerError():

View File

@ -2,9 +2,10 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import escpos.printer as printer
import pytest import pytest
import escpos.printer as printer
@pytest.mark.parametrize( @pytest.mark.parametrize(
"bctype,data", "bctype,data",

View File

@ -9,7 +9,6 @@
import pytest import pytest
from PIL import Image from PIL import Image
import escpos.printer as printer import escpos.printer as printer

View File

@ -1,7 +1,7 @@
#!/usr/bin/python #!/usr/bin/python
"""tests for line display """tests for line display
:author: `Patrick Kanzler <patrick.kanzler@fablab.fau.de>`_ :author: `Patrick Kanzler <dev@pkanzler.de>`_
:organization: `python-escpos <https://github.com/python-escpos>`_ :organization: `python-escpos <https://github.com/python-escpos>`_
:copyright: Copyright (c) 2017 `python-escpos <https://github.com/python-escpos>`_ :copyright: Copyright (c) 2017 `python-escpos <https://github.com/python-escpos>`_
:license: MIT :license: MIT

View File

@ -1,7 +1,7 @@
#!/usr/bin/python #!/usr/bin/python
"""tests for panel button function """tests for panel button function
:author: `Patrick Kanzler <patrick.kanzler@fablab.fau.de>`_ :author: `Patrick Kanzler <dev@pkanzler.de>`_
:organization: `python-escpos <https://github.com/python-escpos>`_ :organization: `python-escpos <https://github.com/python-escpos>`_
:copyright: Copyright (c) 2016 `python-escpos <https://github.com/python-escpos>`_ :copyright: Copyright (c) 2016 `python-escpos <https://github.com/python-escpos>`_
:license: MIT :license: MIT

View File

@ -2,18 +2,18 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""tests for the non-native part of qr() """tests for the non-native part of qr()
:author: `Patrick Kanzler <patrick.kanzler@fablab.fau.de>`_ :author: `Patrick Kanzler <dev@pkanzler.de>`_
:organization: `python-escpos <https://github.com/python-escpos>`_ :organization: `python-escpos <https://github.com/python-escpos>`_
:copyright: Copyright (c) 2016 `python-escpos <https://github.com/python-escpos>`_ :copyright: Copyright (c) 2016 `python-escpos <https://github.com/python-escpos>`_
:license: MIT :license: MIT
""" """
import pytest
import mock import mock
import pytest
from PIL import Image
from escpos.printer import Dummy from escpos.printer import Dummy
from PIL import Image
@mock.patch("escpos.printer.Dummy.image", spec=Dummy) @mock.patch("escpos.printer.Dummy.image", spec=Dummy)

View File

@ -1,9 +1,7 @@
import six import six
import escpos.printer as printer import escpos.printer as printer
from escpos.constants import TXT_NORMAL, TXT_STYLE, SET_FONT from escpos.constants import SET_FONT, TXT_NORMAL, TXT_SIZE, TXT_STYLE
from escpos.constants import TXT_SIZE
# 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

View File

@ -1,9 +1,10 @@
#!/usr/bin/python #!/usr/bin/python
import escpos.printer as printer
import barcode.errors import barcode.errors
import pytest import pytest
import escpos.printer as printer
@pytest.fixture @pytest.fixture
def instance(): def instance():

View File

@ -1,17 +1,18 @@
#!/usr/bin/python #!/usr/bin/python
"""tests for the text printing function """tests for the text printing function
:author: `Patrick Kanzler <patrick.kanzler@fablab.fau.de>`_ :author: `Patrick Kanzler <dev@pkanzler.de>`_
:organization: `python-escpos <https://github.com/python-escpos>`_ :organization: `python-escpos <https://github.com/python-escpos>`_
:copyright: Copyright (c) 2016 `python-escpos <https://github.com/python-escpos>`_ :copyright: Copyright (c) 2016 `python-escpos <https://github.com/python-escpos>`_
:license: MIT :license: MIT
""" """
import pytest
import mock
from hypothesis import given, assume
import hypothesis.strategies as st import hypothesis.strategies as st
import mock
import pytest
from hypothesis import assume, given
from escpos.printer import Dummy from escpos.printer import Dummy

View File

@ -1,4 +1,5 @@
import pytest import pytest
from escpos.printer import Dummy from escpos.printer import Dummy

View File

@ -1,7 +1,7 @@
#!/usr/bin/python #!/usr/bin/python
"""very basic test cases that load the classes """very basic test cases that load the classes
:author: `Patrick Kanzler <patrick.kanzler@fablab.fau.de>`_ :author: `Patrick Kanzler <dev@pkanzler.de>`_
:organization: `python-escpos <https://github.com/python-escpos>`_ :organization: `python-escpos <https://github.com/python-escpos>`_
:copyright: Copyright (c) 2016 `python-escpos <https://github.com/python-escpos>`_ :copyright: Copyright (c) 2016 `python-escpos <https://github.com/python-escpos>`_
:license: MIT :license: MIT

View File

@ -2,19 +2,20 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""tests for the magic encode module """tests for the magic encode module
:author: `Patrick Kanzler <patrick.kanzler@fablab.fau.de>`_ :author: `Patrick Kanzler <dev@pkanzler.de>`_
:organization: `python-escpos <https://github.com/python-escpos>`_ :organization: `python-escpos <https://github.com/python-escpos>`_
:copyright: Copyright (c) 2016 `python-escpos <https://github.com/python-escpos>`_ :copyright: Copyright (c) 2016 `python-escpos <https://github.com/python-escpos>`_
:license: MIT :license: MIT
""" """
import pytest
from hypothesis import given, example
import hypothesis.strategies as st import hypothesis.strategies as st
from escpos.magicencode import MagicEncode, Encoder import pytest
from escpos.katakana import encode_katakana from hypothesis import example, given
from escpos.exceptions import CharCodeError, Error from escpos.exceptions import CharCodeError, Error
from escpos.katakana import encode_katakana
from escpos.magicencode import Encoder, MagicEncode
class TestEncoder: class TestEncoder:

View File

@ -2,16 +2,15 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""tests for the File printer """tests for the File printer
:author: `Patrick Kanzler <patrick.kanzler@fablab.fau.de>`_ :author: `Patrick Kanzler <dev@pkanzler.de>`_
:organization: `python-escpos <https://github.com/python-escpos>`_ :organization: `python-escpos <https://github.com/python-escpos>`_
:copyright: Copyright (c) 2016 `python-escpos <https://github.com/python-escpos>`_ :copyright: Copyright (c) 2016 `python-escpos <https://github.com/python-escpos>`_
:license: MIT :license: MIT
""" """
import six
import pytest import pytest
import six
from hypothesis import given, settings from hypothesis import given, settings
from hypothesis.strategies import text from hypothesis.strategies import text

View File

@ -1,10 +1,12 @@
#!/usr/bin/python #!/usr/bin/python
import escpos.printer as printer
import pytest
import mock
import socket import socket
import mock
import pytest
import escpos.printer as printer
@pytest.fixture @pytest.fixture
def instance(): def instance():

View File

@ -1,5 +1,6 @@
import pytest import pytest
from escpos.capabilities import get_profile, NotSupported, BARCODE_B, Profile
from escpos.capabilities import BARCODE_B, NotSupported, Profile, get_profile
@pytest.fixture @pytest.fixture

View File

@ -1,7 +1,7 @@
#!/usr/bin/python #!/usr/bin/python
"""test the raising of errors with the error module """test the raising of errors with the error module
:author: `Patrick Kanzler <patrick.kanzler@fablab.fau.de>`_ :author: `Patrick Kanzler <dev@pkanzler.de>`_
:organization: `python-escpos <https://github.com/python-escpos>`_ :organization: `python-escpos <https://github.com/python-escpos>`_
:copyright: Copyright (c) 2017 `python-escpos <https://github.com/python-escpos>`_ :copyright: Copyright (c) 2017 `python-escpos <https://github.com/python-escpos>`_
:license: MIT :license: MIT
@ -9,6 +9,7 @@
import pytest import pytest
import escpos import escpos
import escpos.exceptions import escpos.exceptions

View File

@ -1,20 +1,21 @@
#!/usr/bin/python #!/usr/bin/python
"""test the facility which enables usage of the with-statement """test the facility which enables usage of the with-statement
:author: `Patrick Kanzler <patrick.kanzler@fablab.fau.de>`_ :author: `Patrick Kanzler <dev@pkanzler.de>`_
:organization: `python-escpos <https://github.com/python-escpos>`_ :organization: `python-escpos <https://github.com/python-escpos>`_
:copyright: Copyright (c) 2016 `python-escpos <https://github.com/python-escpos>`_ :copyright: Copyright (c) 2016 `python-escpos <https://github.com/python-escpos>`_
:license: MIT :license: MIT
""" """
import escpos.printer as printer
import escpos.escpos as escpos import escpos.escpos as escpos
import escpos.printer as printer
def test_with_statement(): def test_with_statement():
"""Use with statement""" """Use with statement
.. todo:: Extend these tests as they don't really do anything at the moment"""
dummy_printer = printer.Dummy() dummy_printer = printer.Dummy()
with escpos.EscposIO(dummy_printer) as p: with escpos.EscposIO(dummy_printer) as p:
p.writelines("Some text.\n") p.writelines("Some text.\n")
# TODO extend these tests as they don't really do anything at the moment

16
tox.ini
View File

@ -22,8 +22,9 @@ deps = jaconv
pytest-mock pytest-mock
hypothesis>4 hypothesis>4
python-barcode python-barcode
commands = pytest --cov escpos --cov-report=xml commands = pytest
passenv = ESCPOS_CAPABILITIES_PICKLE_DIR, ESCPOS_CAPABILITIES_FILE, CI, TRAVIS, TRAVIS_*, APPVEYOR, APPVEYOR_*, CODECOV_* passenv = ESCPOS_CAPABILITIES_PICKLE_DIR, ESCPOS_CAPABILITIES_FILE, CI, TRAVIS, TRAVIS_*, APPVEYOR, APPVEYOR_*, CODECOV_*
setenv = PY_IGNORE_IMPORTMISMATCH=1
[testenv:docs] [testenv:docs]
basepython = python basepython = python
@ -39,7 +40,16 @@ commands = sphinx-build -W -b html -d {envtmpdir}/doctrees . {envtmpdir}/html
[testenv:flake8] [testenv:flake8]
basepython = python basepython = python
# TODO add flake8-future
# TODO add flake8-docstrings
deps = flake8 deps = flake8
flake8-docstrings
commands = flake8 commands = flake8
[testenv:mypy]
basepython = python
deps = mypy
types-six
types-PyYAML
types-appdirs
types-Pillow
jaconv
commands = mypy src