diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index ec332953..b2696611 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -30,6 +30,11 @@ jobs: python -m pip install --upgrade pip pip install flake8 pytest tox tox-gh-actions 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 run: | # stop the build if there are Python syntax errors or undefined names @@ -50,4 +55,4 @@ jobs: files: ./coverage.xml,!./cache flags: unittests name: coverage-tox-${{ matrix.python-version }} - verbose: true \ No newline at end of file + verbose: true diff --git a/doc/conf.py b/doc/conf.py index 5fc61017..0c07a382 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -12,9 +12,8 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import sys import os - +import sys from importlib.metadata import version as imp_version on_rtd = os.getenv("READTHEDOCS") == "True" diff --git a/examples/barcodes.py b/examples/barcodes.py index 16c80835..cd905fcf 100644 --- a/examples/barcodes.py +++ b/examples/barcodes.py @@ -1,6 +1,6 @@ +"""Example for printing barcodes.""" from escpos.printer import Usb - # Adapt to your needs p = Usb(0x0416, 0x5011, profile="TM-T88II") diff --git a/examples/codepage_tables.py b/examples/codepage_tables.py index dcec2a33..71289423 100644 --- a/examples/codepage_tables.py +++ b/examples/codepage_tables.py @@ -1,23 +1,24 @@ -"""Prints code page tables. -""" +"""Prints code page tables.""" -import six import sys +import six + from escpos import printer from escpos.constants import ( CODEPAGE_CHANGE, - ESC, - CTL_LF, - CTL_FF, CTL_CR, + CTL_FF, CTL_HT, + CTL_LF, CTL_VT, + ESC, ) def main(): + """Init printer and print codepage tables.""" dummy = printer.Dummy() dummy.hw("init") @@ -34,6 +35,7 @@ def main(): def print_codepage(printer, codepage): + """Print a codepage.""" if codepage.isdigit(): codepage = int(codepage) printer._raw(CODEPAGE_CHANGE + six.int2byte(codepage)) diff --git a/examples/docker-flask/app.py b/examples/docker-flask/app.py index dc66b3fc..2f13cb22 100644 --- a/examples/docker-flask/app.py +++ b/examples/docker-flask/app.py @@ -1,12 +1,15 @@ -from escpos.printer import CupsPrinter +"""Example for a flask application.""" from flask import Flask +from escpos.printer import CupsPrinter + # Initialize Flask app app = Flask(__name__) @app.route("/", methods=["GET"]) def do_print(): + """Print.""" # p = Usb(0x04b8, 0x0e28, 0) p = CupsPrinter(host="localhost", port=631, printer_name="TM-T20III") p.text("Hello World\n") diff --git a/examples/docker-flask/requirements.txt b/examples/docker-flask/requirements.txt index bb449326..5fdb1c6e 100644 --- a/examples/docker-flask/requirements.txt +++ b/examples/docker-flask/requirements.txt @@ -3,7 +3,6 @@ argcomplete==3.0.8 blinker==1.6.2 click==8.1.3 Flask==2.3.2 -future==0.18.3 itsdangerous==2.1.2 Jinja2==3.1.2 MarkupSafe==2.1.2 diff --git a/examples/qr_code.py b/examples/qr_code.py index 24b9d606..85f97118 100644 --- a/examples/qr_code.py +++ b/examples/qr_code.py @@ -1,9 +1,11 @@ +"""Print example QR codes.""" import sys from escpos.printer import Usb def usage(): + """Print information on usage.""" print("usage: qr_code.py ") diff --git a/examples/software_barcode.py b/examples/software_barcode.py index 7c949dd8..1428320b 100644 --- a/examples/software_barcode.py +++ b/examples/software_barcode.py @@ -1,6 +1,6 @@ +"""Example file for software barcodes.""" from escpos.printer import Usb - # Adapt to your needs p = Usb(0x0416, 0x5011, profile="POS-5890") diff --git a/examples/weather.py b/examples/weather.py index 033f3b96..23a06198 100644 --- a/examples/weather.py +++ b/examples/weather.py @@ -1,28 +1,29 @@ #!/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 -# 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 +""" -# Icons taken from https://adamwhitcroft.com/climacons/ -# Check out his github: https://github.com/AdamWhitcroft/climacons - -from datetime import datetime import calendar -import urllib import json -import time import os +import time +import urllib +from datetime import datetime from escpos.printer import Usb -""" Setting up the main pathing """ +"""Set up the main pathing.""" this_dir, this_filename = os.path.split(__file__) GRAPHICS_PATH = os.path.join(this_dir, "graphics/climacons/") @@ -38,13 +39,14 @@ def forecast_icon(idx): + """Get right icon for forecast.""" icon = data["daily"]["data"][idx]["icon"] image = GRAPHICS_PATH + icon + ".png" return image -# Dumps one forecast line to the printer def forecast(idx): + """Dump one forecast line to the printer.""" date = datetime.fromtimestamp(int(data["daily"]["data"][idx]["time"])) day = calendar.day_name[date.weekday()] lo = data["daily"]["data"][idx]["temperatureMin"] @@ -73,6 +75,7 @@ def forecast(idx): def icon(): + """Get icon.""" icon = data["currently"]["icon"] image = GRAPHICS_PATH + icon + ".png" return image diff --git a/pyproject.toml b/pyproject.toml index d9101a0c..8bbdf687 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,15 @@ [tool.black] 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", +] diff --git a/setup.cfg b/setup.cfg index 4d8c4dc0..ddc4195f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -31,7 +31,7 @@ project_urls = Release Notes = https://github.com/python-escpos/python-escpos/releases [options] -python_requires = >=3.6 +python_requires = >=3.8 zip_safe = false include_package_data = true install_requires = @@ -46,7 +46,6 @@ install_requires = PyYAML argparse argcomplete - future importlib_resources setup_requires = setuptools_scm 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 max-line-length = 120 extend-ignore = E203, W503 -# future-imports = absolute_import, division, print_function, unicode_literals # we are not there yet diff --git a/setup.py b/setup.py index 898a51b5..c1cec16d 100755 --- a/setup.py +++ b/setup.py @@ -1,9 +1,10 @@ #!/usr/bin/env python +"""Setup script for python package.""" import os import sys -from setuptools import find_packages, setup +from setuptools import find_packages, setup base_dir = os.path.dirname(__file__) src_dir = os.path.join(base_dir, "src") @@ -14,14 +15,18 @@ 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() setuptools_scm_template = """\ -# coding: utf-8 -# file generated by setuptools_scm -# don't change, don't track in version control +#!/usr/bin/python +# -*- coding: utf-8 -*- +\"\"\"Version identifier. + +file generated by setuptools_scm +don't change, don't track in version control +\"\"\" version = '{version}' """ diff --git a/src/escpos/__init__.py b/src/escpos/__init__.py index bf739e5a..286a6e65 100644 --- a/src/escpos/__init__.py +++ b/src/escpos/__init__.py @@ -1,7 +1,5 @@ # -*- coding: utf-8 -*- -""" -python-escpos enables you to manipulate escpos-printers -""" +"""python-escpos enables you to manipulate escpos-printers.""" __all__ = ["constants", "escpos", "exceptions", "printer"] diff --git a/src/escpos/capabilities.py b/src/escpos/capabilities.py index 7294717c..dcd7a64c 100644 --- a/src/escpos/capabilities.py +++ b/src/escpos/capabilities.py @@ -1,19 +1,18 @@ -import re -from os import environ, path +"""Handler for capabilities data.""" import atexit -import pickle import logging +import pickle +import platform +import re import time -import importlib_resources - -import six -import yaml - from contextlib import ExitStack +from os import environ, path from tempfile import mkdtemp -import platform +from typing import Any, Dict, Optional -from typing import Any, Dict +import importlib_resources +import six +import yaml logging.basicConfig() logger = logging.getLogger(__name__) @@ -60,9 +59,7 @@ class NotSupported(Exception): - """Raised if a requested feature is not supported by the - printer profile. - """ + """Raised if a requested feature is not supported by the printer profile.""" pass @@ -80,11 +77,13 @@ class BaseProfile(object): profile_data: Dict[str, Any] = {} def __getattr__(self, name): + """Get a data element from the profile.""" return self.profile_data[name] def get_font(self, font) -> int: - """Return the escpos index for `font`. Makes sure that - the requested `font` is valid. + """Return the escpos index for `font`. + + Makes sure that the requested `font` is valid. """ font = {"a": 0, "b": 1}.get(font, font) if not six.text_type(font) in self.fonts: @@ -107,9 +106,10 @@ def get_code_pages(self): return {v: k for k, v in self.codePages.items()} -def get_profile(name: str = None, **kwargs): - """Get the profile by name; if no name is given, return the - default profile. +def get_profile(name: Optional[str] = None, **kwargs): + """Get a profile by name. + + If no name is given, return the default profile. """ if isinstance(name, Profile): return name @@ -122,7 +122,9 @@ def get_profile(name: str = None, **kwargs): 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. """ if name not in CLASS_CACHE: @@ -136,6 +138,7 @@ def get_profile_class(name: str): def clean(s): + """Clean profile name.""" # Remove invalid characters s = re.sub("[^0-9a-zA-Z_]", "", s) # Remove leading characters until we find a letter or underscore @@ -144,17 +147,20 @@ def clean(s): class Profile(get_profile_class("default")): - """ - For users, who want to provide their profile + """Profile class for user usage. + + For users, who want to provide their own profile. """ def __init__(self, columns=None, features=None): + """Initialize profile.""" super(Profile, self).__init__() self.columns = columns self.features = features or {} def get_columns(self, font): + """Get column count of printer.""" if self.columns is not None: return self.columns diff --git a/src/escpos/cli.py b/src/escpos/cli.py index 64eb0773..321fa011 100644 --- a/src/escpos/cli.py +++ b/src/escpos/cli.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # PYTHON_ARGCOMPLETE_OK -""" CLI +"""CLI. 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. @@ -18,15 +18,18 @@ # this CLI works nevertheless without argcomplete pass # noqa import sys + import six -from . import config -from . import version + +from . import config, version # Must be defined before it's used in DEMO_FUNCTIONS def str_to_bool(string): - """Used as a type in argparse so that we get back a proper - bool instead of always True + """Convert string to Bool. + + 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") @@ -452,13 +455,11 @@ def str_to_bool(string): def main(): - """ + """Handle main entry point of CLI script. Handles loading of configuration and creating and processing of command line arguments. Called when run from a CLI. - """ - parser = argparse.ArgumentParser( description="CLI for python-escpos", epilog="Printer configuration is defined in the python-escpos config" @@ -569,8 +570,9 @@ def main(): def demo(printer, **kwargs): - """ - Prints demos. Called when CLI is passed `demo`. This function + """Print demos. + + Called when CLI is passed `demo`. This function uses the DEMO_FUNCTIONS dictionary. :param printer: A printer from escpos.printer diff --git a/src/escpos/codepages.py b/src/escpos/codepages.py index f858b08c..ca7e002b 100644 --- a/src/escpos/codepages.py +++ b/src/escpos/codepages.py @@ -1,23 +1,31 @@ +"""Helper module for codepage handling.""" from .capabilities import CAPABILITIES class CodePageManager: - """Holds information about all the code pages (as defined - in escpos-printer-db). + """Holds information about all the code pages. + + Information as defined in escpos-printer-db. """ def __init__(self, data): + """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): - # TODO resolve the encoding alias + """Get encoding name. + + .. todo:: Resolve the encoding alias. + """ return encoding.upper() def get_encoding(self, encoding): + """Return the encoding data.""" return self.data[encoding] diff --git a/src/escpos/config.py b/src/escpos/config.py index 8fdafb0f..8649daef 100644 --- a/src/escpos/config.py +++ b/src/escpos/config.py @@ -1,16 +1,13 @@ -""" ESC/POS configuration manager. +"""ESC/POS configuration manager. This module contains the implementations of abstract base class :py:class:`Config`. - """ - - import os + import appdirs import yaml -from . import printer -from . import exceptions +from . import exceptions, printer class Config(object): @@ -48,13 +45,11 @@ def _reset_config(self): self._printer_config = 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 for the configuration file. - """ - self._reset_config() if not config_path: @@ -96,8 +91,9 @@ def load(self, config_path=None): self._has_loaded = True def printer(self): - """Returns a printer that was defined in the config, or throws an - exception. + """Return a printer that was defined in the config. + + Throw an exception on error. This method loads the default config if one hasn't beeen already loaded. diff --git a/src/escpos/escpos.py b/src/escpos/escpos.py index 9b46011c..50af002f 100644 --- a/src/escpos/escpos.py +++ b/src/escpos/escpos.py @@ -1,6 +1,6 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -""" Main class +"""Main class. This module contains the abstract base class :py:class:`Escpos`. @@ -11,80 +11,88 @@ """ -import qrcode import textwrap -import six +from abc import ABCMeta, abstractmethod # abstract base class support from re import match as re_match +from typing import Union import barcode +import qrcode +import six from barcode.writer import ImageWriter +from escpos.capabilities import get_profile +from escpos.image import EscposImage + from .constants import ( + BARCODE_FONT_A, + BARCODE_FONT_B, + BARCODE_FORMATS, + BARCODE_HEIGHT, + BARCODE_TXT_ABV, + BARCODE_TXT_BLW, + BARCODE_TXT_BTH, + BARCODE_TXT_OFF, + BARCODE_TYPES, + BARCODE_WIDTH, + BUZZER, + CD_KICK_2, + CD_KICK_5, + CD_KICK_DEC_SEQUENCE, + CTL_CR, + CTL_FF, + CTL_LF, + 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_ON, + PAPER_FULL_CUT, + PAPER_PART_CUT, + QR_ECLEVEL_H, QR_ECLEVEL_L, QR_ECLEVEL_M, - QR_ECLEVEL_H, 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, - SLIP_EJECT, + TXT_NORMAL, + TXT_SIZE, + TXT_STYLE, ) -from .constants import ( - QR_MODEL_1, - QR_MODEL_2, - QR_MICRO, - BARCODE_TYPES, - BARCODE_HEIGHT, - BARCODE_WIDTH, +from .exceptions import ( + BarcodeCodeError, + BarcodeSizeError, + BarcodeTypeError, + CashDrawerError, + ImageWidthError, + SetVariableError, + TabPosError, ) -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_BLW, -) -from .constants import TXT_SIZE, TXT_NORMAL -from .constants import SET_FONT -from .constants import LINESPACING_FUNCS, LINESPACING_RESET -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, -) -from .constants import HW_RESET, HW_SELECT, HW_INIT -from .constants import ( - CTL_VT, - CTL_CR, - CTL_FF, - CTL_LF, - CTL_SET_HT, - PANEL_BUTTON_OFF, - PANEL_BUTTON_ON, -) -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 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, # convert to uppercase and map them to their original names. HW_BARCODE_NAMES = { @@ -100,7 +108,7 @@ @six.add_metaclass(ABCMeta) class Escpos(object): - """ESC/POS Printer object + """ESC/POS Printer object. This class is the abstract base class for an esc/pos-printer. The printer implementations are children of this class. @@ -109,19 +117,20 @@ class Escpos(object): device = None 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.magic = MagicEncode(self, **(magic_encode_args or {})) def __del__(self): - """call self.close upon deletion""" + """Call self.close upon deletion.""" self.close() @abstractmethod - def _raw(self, msg): - """Sends raw data to the printer + def _raw(self, msg: bytes): + """Send raw data to the printer. This function has to be individually implemented by the implementations. @@ -131,7 +140,9 @@ def _raw(self, msg): pass 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 """ raise NotImplementedError() @@ -145,7 +156,7 @@ def image( fragment_height=960, 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. When printing in low density, the image will be stretched. @@ -252,8 +263,7 @@ def image( self._raw(b"".join(outp)) def _image_send_graphics_data(self, m, fn, data): - """ - Wrapper for GS ( L, to calculate and send correct data length. + """Calculate and send correct data length for `GS ( L`. :param m: Modifier//variant for function. Usually '0' :param fn: Function number to use, as byte @@ -272,7 +282,7 @@ def qr( center=False, 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 ec: Error-correction level to use. One of QR_ECLEVEL_L (default), QR_ECLEVEL_M, QR_ECLEVEL_Q or @@ -345,7 +355,7 @@ def qr( self._send_2d_code_data(six.int2byte(81), cn, b"", b"0") 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 cn: Output code type. Affects available data. @@ -379,8 +389,8 @@ def _int_low_high(inp_number, out_bytes): inp_number //= 256 return outp - def charcode(self, code="AUTO"): - """Set Character Code Table + def charcode(self, code: str = "AUTO"): + """Set Character Code Table. 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 @@ -396,7 +406,8 @@ def charcode(self, code="AUTO"): @staticmethod def check_barcode(bc, code): - """ + """Check if barcode is OK. + 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 full set of requirement for each barcode type is available in the ESC/POS documentation. @@ -444,15 +455,15 @@ def barcode( self, code, bc, - height=64, - width=3, - pos="BELOW", - font="A", - align_ct=True, + height: int = 64, + width: int = 3, + pos: str = "BELOW", + font: str = "A", + align_ct: bool = True, function_type=None, - check=True, - force_software=False, - ): + check: bool = True, + force_software: Union[bool, str] = False, + ) -> None: """Print barcode. Automatic hardware|software barcode renderer according to the printer capabilities. @@ -535,9 +546,11 @@ def barcode( raise BarcodeTypeError(f"Not supported or wrong barcode name {bc}.") 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 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 impl = force_software print(f"Using {impl} software barcode renderer") @@ -567,15 +580,15 @@ def _hw_barcode( self, code, bc, - height=64, - width=3, - pos="BELOW", - font="A", - align_ct=True, + height: int = 64, + width: int = 3, + pos: str = "BELOW", + font: str = "A", + align_ct: bool = True, 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 be supported by the unit. By default, this method will check whether your barcode text is correct, that is @@ -726,14 +739,14 @@ def _sw_barcode( self, barcode_type, data, - impl="bitImageColumn", - module_height=5, - module_width=0.2, - text_distance=5, - font_size=10, - center=True, + impl: str = "bitImageColumn", + module_height: Union[int, float] = 5, + module_width: Union[int, float] = 0.2, + text_distance: Union[int, float] = 5, + font_size: int = 10, + center: bool = True, ): - """Print Barcode + """Print Barcode. 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 @@ -773,13 +786,13 @@ def _sw_barcode( * bitImageRaster :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). - :type module_width: int | float + :type module_width: Union[int, float] :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). :type font_size: int @@ -817,7 +830,7 @@ def _sw_barcode( self.image(image, impl=impl, center=center) def text(self, txt): - """Print alpha-numeric text + """Print alpha-numeric text. The text has to be encoded in the currently selected codepage. The input text has to be encoded in unicode. @@ -829,7 +842,7 @@ def text(self, txt): self.magic.write(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 input text has to be encoded in unicode. @@ -840,7 +853,7 @@ def textln(self, txt=""): self.text("{}\n".format(txt)) def ln(self, count=1): - """Print a newline or more + """Print a newline or more. :param count: number of newlines to print :raises: :py:exc:`ValueError` if count < 0 @@ -851,7 +864,7 @@ def ln(self, count=1): self.text("\n" * count) 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. @@ -879,7 +892,7 @@ def set( double_height=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: @@ -918,7 +931,6 @@ def set( :type height: int :type density: int """ - if custom_size: if ( 1 <= width <= 8 @@ -996,7 +1008,6 @@ def cut(self, mode="FULL", feed=True): :param feed: print and feed before cutting. default: true :raises ValueError: if mode not in ('FULL', 'PART') """ - if not feed: self._raw(GS + b"V" + six.int2byte(66) + b"\x00") return @@ -1019,7 +1030,7 @@ def cut(self, mode="FULL", feed=True): self._raw(PAPER_PART_CUT) 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`) or pin 5 (:py:const:`~escpos.constants.CD_KICK_5`) @@ -1041,7 +1052,7 @@ def cashdraw(self, pin): raise CashDrawerError(err) 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. If you set `select_display` to true, only the display is selected and if you set it to false, @@ -1056,15 +1067,14 @@ def linedisplay_select(self, select_display=False): self._raw(LINE_DISPLAY_CLOSE) 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. """ self._raw(LINE_DISPLAY_CLEAR) 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 the display between your computer and printer. @@ -1077,7 +1087,7 @@ def linedisplay(self, text): self.linedisplay_select(select_display=False) def hw(self, hw): - """Hardware operations + """Hardware operations. :param hw: hardware action, may be: @@ -1095,7 +1105,7 @@ def hw(self, hw): pass 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. @@ -1109,7 +1119,7 @@ def print_and_feed(self, n=1): raise ValueError("n must be betwen 0 and 255") def control(self, ctl, count=5, tab_size=8): - """Feed control sequences + """Feed control sequences. :param ctl: string for the following control sequences: @@ -1145,7 +1155,7 @@ def control(self, ctl, count=5, tab_size=8): self._raw(CTL_VT) 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 will be disabled. @@ -1175,9 +1185,9 @@ def panel_buttons(self, enable=True): self._raw(PANEL_BUTTON_OFF) def query_status(self, mode): - """ - Queries the printer for its status, and returns an array - of integers containing it. + """Query the printer for its status. + + Returns an array of integers containing it. :param mode: Integer that sets the status mode queried to the printer. - RT_STATUS_ONLINE: Printer status. @@ -1189,8 +1199,7 @@ def query_status(self, mode): return status def is_online(self): - """ - Queries the online status of the printer. + """Query the online status of the printer. :returns: When online, returns ``True``; ``False`` otherwise. :rtype: bool @@ -1201,8 +1210,7 @@ def is_online(self): return not (status[0] & RT_MASK_ONLINE) def paper_status(self): - """ - Queries the paper status of the printer. + """Query the paper status of the printer. Returns 2 if there is plenty of paper, 1 if the paper has arrived to the near-end sensor and 0 if there is no paper. @@ -1221,7 +1229,7 @@ def paper_status(self): return 2 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 slip dot matrix printer if supported (SLIP) @@ -1234,11 +1242,11 @@ def target(self, type="ROLL"): raise ValueError("Unsupported target") def eject_slip(self): - """Eject the slip/cheque""" + """Eject the slip/cheque.""" self._raw(SLIP_EJECT) 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 sensor is covered, reverses the slip out the front of the printer @@ -1248,7 +1256,7 @@ def print_and_eject_slip(self): self._raw(SLIP_PRINT_AND_EJECT) 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 is initialized or the Clear Printer (0x10) command is received @@ -1256,7 +1264,7 @@ def use_slip_only(self): self._raw(SLIP_SELECT) 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, which means how many times the buzzer will be 'beeped'. @@ -1265,7 +1273,6 @@ def buzzer(self, times=2, duration=4): :param duration: Integer between 1 and 9, indicates the beep duration. :returns: None """ - if not 1 <= times <= 9: raise ValueError("times must be between 1 and 9") if not 1 <= duration <= 9: @@ -1275,7 +1282,7 @@ def buzzer(self, times=2, duration=4): 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 and assign it to the EscposIO class. @@ -1295,7 +1302,8 @@ class EscposIO(object): """ def __init__(self, printer, autocut=True, autoclose=True, **kwargs): - """ + """Initialize object. + :param printer: An EscPos-printer object :type printer: escpos.Escpos :param autocut: If True, paper is automatically cut after the `with`-statement *default*: True @@ -1307,7 +1315,7 @@ def __init__(self, printer, autocut=True, autoclose=True, **kwargs): self.autoclose = autoclose def set(self, **kwargs): - """Set the printer-parameters + """Set the printer-parameters. Controls which parameters will be passed to :py:meth:`Escpos.set() `. For more information on the parameters see the :py:meth:`set() `-methods @@ -1319,6 +1327,7 @@ def set(self, **kwargs): self.params.update(kwargs) def writelines(self, text, **kwargs): + """Print text.""" params = dict(self.params) params.update(kwargs) @@ -1341,14 +1350,18 @@ def writelines(self, text, **kwargs): self.printer.text("{0}\n".format(line)) def close(self): - """called upon closing the `with`-statement""" + """Close printer. + + Called upon closing the `with`-statement. + """ self.printer.close() def __enter__(self, **kwargs): + """Enter context.""" return self def __exit__(self, type, value, traceback): - """ + """Cut and close if configured. If :py:attr:`autocut ` is `True` (set by this class' constructor), then :py:meth:`printer.cut() ` will be called here. diff --git a/src/escpos/exceptions.py b/src/escpos/exceptions.py index cdb7eeae..8a357d5a 100644 --- a/src/escpos/exceptions.py +++ b/src/escpos/exceptions.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -""" ESC/POS Exceptions classes +"""ESC/POS Exceptions classes. Result/Exit codes: @@ -27,9 +27,10 @@ class Error(Exception): - """Base class for ESC/POS errors""" + """Base class for ESC/POS errors.""" def __init__(self, msg, status=None): + """Initialize Error object.""" Exception.__init__(self) self.msg = msg self.resultcode = 1 @@ -37,6 +38,7 @@ def __init__(self, msg, status=None): self.resultcode = status def __str__(self): + """Return string representation of Error.""" return self.msg @@ -49,11 +51,13 @@ class BarcodeTypeError(Error): """ def __init__(self, msg=""): + """Initialize BarcodeTypeError object.""" Error.__init__(self, msg) self.msg = msg self.resultcode = 10 def __str__(self): + """Return string representation of BarcodeTypeError.""" return "No Barcode type is defined ({msg})".format(msg=self.msg) @@ -66,11 +70,13 @@ class BarcodeSizeError(Error): """ def __init__(self, msg=""): + """Initialize BarcodeSizeError object.""" Error.__init__(self, msg) self.msg = msg self.resultcode = 20 def __str__(self): + """Return string representation of BarcodeSizeError.""" return "Barcode size is out of range ({msg})".format(msg=self.msg) @@ -83,11 +89,13 @@ class BarcodeCodeError(Error): """ def __init__(self, msg=""): + """Initialize BarcodeCodeError object.""" Error.__init__(self, msg) self.msg = msg self.resultcode = 30 def __str__(self): + """Return string representation of BarcodeCodeError.""" return "No Barcode code was supplied ({msg})".format(msg=self.msg) @@ -98,11 +106,13 @@ class ImageSizeError(Error): """ def __init__(self, msg=""): + """Initialize ImageSizeError object.""" Error.__init__(self, msg) self.msg = msg self.resultcode = 40 def __str__(self): + """Return string representation of ImageSizeError.""" return "Image height is longer than 255px and can't be printed ({msg})".format( msg=self.msg ) @@ -115,11 +125,13 @@ class ImageWidthError(Error): """ def __init__(self, msg=""): + """Initialize ImageWidthError object.""" Error.__init__(self, msg) self.msg = msg self.resultcode = 41 def __str__(self): + """Return string representation of ImageWidthError.""" return "Image width is too large ({msg})".format(msg=self.msg) @@ -131,11 +143,13 @@ class TextError(Error): """ def __init__(self, msg=""): + """Initialize TextError object.""" Error.__init__(self, msg) self.msg = msg self.resultcode = 50 def __str__(self): + """Return string representation of TextError.""" return "Text string must be supplied to the text() method ({msg})".format( msg=self.msg ) @@ -149,16 +163,20 @@ class CashDrawerError(Error): """ def __init__(self, msg=""): + """Initialize CashDrawerError object.""" Error.__init__(self, msg) self.msg = msg self.resultcode = 60 def __str__(self): + """Return string representation of CashDrawerError.""" return "Valid pin must be set to send pulse ({msg})".format(msg=self.msg) 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. This exception is raised by :py:meth:`escpos.escpos.Escpos.control`. @@ -166,11 +184,13 @@ class TabPosError(Error): """ def __init__(self, msg=""): + """Initialize TabPosError object.""" Error.__init__(self, msg) self.msg = msg self.resultcode = 70 def __str__(self): + """Return string representation of TabPosError.""" return "Valid tab positions must be in the range 0 to 16 ({msg})".format( msg=self.msg ) @@ -184,43 +204,49 @@ class CharCodeError(Error): """ def __init__(self, msg=""): + """Initialize CharCodeError object.""" Error.__init__(self, msg) self.msg = msg self.resultcode = 80 def __str__(self): + """Return string representation of CharCodeError.""" return "Valid char code must be set ({msg})".format(msg=self.msg) 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. Ths returncode for this exception is `90`. """ def __init__(self, msg=""): + """Initialize USBNotFoundError object.""" Error.__init__(self, msg) self.msg = msg self.resultcode = 90 def __str__(self): + """Return string representation of USBNotFoundError.""" return "USB device not found ({msg})".format(msg=self.msg) 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 Ths returncode for this exception is `100`. """ def __init__(self, msg=""): + """Initialize SetVariableError object.""" Error.__init__(self, msg) self.msg = msg self.resultcode = 100 def __str__(self): + """Return string representation of SetVariableError.""" return "Set variable out of range ({msg})".format(msg=self.msg) @@ -228,48 +254,54 @@ def __str__(self): 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 Ths returncode for this exception is `200`. """ def __init__(self, msg=""): + """Initialize ConfigNotFoundError object.""" Error.__init__(self, msg) self.msg = msg self.resultcode = 200 def __str__(self): + """Return string representation of ConfigNotFoundError.""" return "Configuration not found ({msg})".format(msg=self.msg) class ConfigSyntaxError(Error): - """The configuration file is invalid + """The configuration file is invalid. The syntax is incorrect Ths returncode for this exception is `210`. """ def __init__(self, msg=""): + """Initialize ConfigSyntaxError object.""" Error.__init__(self, msg) self.msg = msg self.resultcode = 210 def __str__(self): + """Return string representation of ConfigSyntaxError.""" return "Configuration syntax is invalid ({msg})".format(msg=self.msg) 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 Ths returncode for this exception is `220`. """ def __init__(self, msg=""): + """Initialize ConfigSectionMissingError object.""" Error.__init__(self, msg) self.msg = msg self.resultcode = 220 def __str__(self): + """Return string representation of ConfigSectionMissingError.""" return "Configuration section is missing ({msg})".format(msg=self.msg) diff --git a/src/escpos/image.py b/src/escpos/image.py index f622ff81..a5dbfea5 100644 --- a/src/escpos/image.py +++ b/src/escpos/image.py @@ -1,4 +1,4 @@ -""" Image format handling class +"""Image format handling class. This module contains the image format handler :py:class:`EscposImage`. @@ -10,6 +10,7 @@ import math + from PIL import Image, ImageOps @@ -22,8 +23,7 @@ class EscposImage(object): """ def __init__(self, img_source): - """ - Load in an image + """Load in an image. :param img_source: PIL.Image, or filename to load one from. """ @@ -49,30 +49,23 @@ def __init__(self, img_source): @property def width(self): - """ - Width of image in pixels - """ + """Return width of image in pixels.""" width_pixels, _ = self._im.size return width_pixels @property def width_bytes(self): - """ - Width of image if you use 8 pixels per byte and 0-pad at the end. - """ + """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): - """ - Height of image in pixels - """ + """Height of image in pixels.""" _, height_pixels = self._im.size return height_pixels 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 """ @@ -89,14 +82,11 @@ def to_column_format(self, high_density_vertical=True): left += line_height def to_raster_format(self): - """ - Convert image to raster-format binary - """ + """Convert image to raster-format binary.""" return self._im.tobytes() 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 :return: list of PIL objects @@ -113,7 +103,7 @@ def split(self, fragment_height): return fragments def center(self, max_width): - """In-place image centering + """Center image in place. :param: Maximum width in order to deduce x offset for centering :return: None diff --git a/src/escpos/magicencode.py b/src/escpos/magicencode.py index bc0bb7f6..cdb2682d 100644 --- a/src/escpos/magicencode.py +++ b/src/escpos/magicencode.py @@ -1,6 +1,6 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -""" Magic Encode +"""Magic Encode. 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. @@ -13,17 +13,18 @@ """ +import re from builtins import bytes + +import six + +from .codepages import CodePages from .constants import CODEPAGE_CHANGE from .exceptions import Error -from .codepages import CodePages -import six -import re class Encoder(object): - """Takes a list of available code spaces. Picks the right one for a - given character. + """Take available code spaces and pick the right one for a given character. Note: To determine the code page, it needs to do the conversion, and thus already knows what the final byte in the target encoding would @@ -39,20 +40,24 @@ class Encoder(object): """ def __init__(self, codepage_map): + """Initialize encoder.""" self.codepages = codepage_map self.available_encodings = set(codepage_map.keys()) self.available_characters = {} self.used_encodings = set() def get_sequence(self, encoding): + """Get a sequence.""" return int(self.codepages[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 is supported. - TODO: Support encoding aliases: pc437 instead of cp437. + .. todo:: Support encoding aliases: pc437 instead of cp437. """ encoding = CodePages.get_encoding_name(encoding) if encoding not in self.codepages: @@ -66,7 +71,7 @@ def get_encoding_name(self, encoding): @staticmethod 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. @@ -92,7 +97,7 @@ def _get_codepage_char_list(encoding): raise LookupError("Can't find a known encoding for {}".format(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 in this encoding. @@ -130,7 +135,7 @@ def can_encode(self, encoding, char): @staticmethod 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 charmap: dictionary for mapping characters in this code page @@ -142,7 +147,7 @@ def _encode_char(char, charmap, defaultchar): return ord(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 encoding: Encoding name to use (must be defined in capabilities) @@ -159,7 +164,9 @@ def __encoding_sort_func(self, item): return (key in self.used_encodings, index) 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 chance they might work again, reducing the search space, @@ -184,7 +191,9 @@ def find_suitable_encoding(self, char): 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). """ if not encoding: @@ -199,7 +208,9 @@ def split_writable_text(encoder, text, encoding): 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. This will consider the printers supported codepages, according @@ -213,7 +224,7 @@ class MagicEncode(object): def __init__( self, driver, encoding=None, disabled=False, defaultsymbol="?", encoder=None ): - """ + """Initialize magic encode. :param driver: :param encoding: If you know the current encoding of the printer @@ -235,7 +246,7 @@ def __init__( self.disabled = disabled 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. However, it will still keep track of the current code page. @@ -248,7 +259,6 @@ def force_encoding(self, encoding): def write(self, text): """Write the text, automatically switching encodings.""" - if self.disabled: self.write_with_encoding(self.encoding, text) return @@ -277,12 +287,16 @@ def write(self, text): self.write_with_encoding(encoding, to_write) 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 # unnecesary codepage switches. self.write(self.defaultsymbol) 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: raise Error( "The supplied text has to be unicode, but is of type {type}.".format( diff --git a/src/escpos/printer.py b/src/escpos/printer.py index 913176cd..93eb9aa0 100644 --- a/src/escpos/printer.py +++ b/src/escpos/printer.py @@ -1,6 +1,6 @@ #!/usr/bin/python # -*- 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 `_ and others :organization: Bashlinux and `python-escpos `_ @@ -31,16 +31,17 @@ _CUPSPRINT = False try: - import cups import tempfile + import cups + _CUPSPRINT = True except ImportError: pass class Usb(Escpos): - """USB printer + """USB printer. This class describes a printer that natively speaks USB. @@ -62,7 +63,8 @@ def __init__( *args, **kwargs ): # noqa: N803 - """ + """Initialize USB printer. + :param idVendor: Vendor ID :param idProduct: Product ID :param usb_args: Optional USB arguments (e.g. custom_match) @@ -122,7 +124,7 @@ def open(self, usb_args): print("Could not set configuration: {0}".format(str(e))) 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 :type msg: bytes @@ -130,18 +132,18 @@ def _raw(self, msg): self.device.write(self.out_ep, msg, self.timeout) 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) def close(self): - """Release USB interface""" + """Release USB interface.""" if self.device: usb.util.dispose_resources(self.device) self.device = None class Serial(Escpos): - """Serial printer + """Serial printer. This class describes a printer that is connected by serial interface. @@ -165,7 +167,7 @@ def __init__( *args, **kwargs ): - """ + """Initialize serial printer. :param devfile: Device file under dev filesystem :param baudrate: Baud rate for serial transmission @@ -189,7 +191,7 @@ def __init__( self.open() 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: self.close() self.device = serial.Serial( @@ -209,7 +211,7 @@ def open(self): print("Unable to open serial printer on: {0}".format(str(self.devfile))) 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 :type msg: bytes @@ -217,18 +219,18 @@ def _raw(self, msg): self.device.write(msg) 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) def close(self): - """Close Serial interface""" + """Close Serial interface.""" if self.device is not None and self.device.is_open: self.device.flush() self.device.close() 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 is forwarded with ``socat``. @@ -251,7 +253,7 @@ class Network(Escpos): """ def __init__(self, host, port=9100, timeout=60, *args, **kwargs): - """ + """Initialize network printer. :param host: Printer's hostname or IP address :param port: Port to write to @@ -264,7 +266,7 @@ def __init__(self, host, port=9100, timeout=60, *args, **kwargs): self.open() 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.settimeout(self.timeout) self.device.connect((self.host, self.port)) @@ -273,7 +275,7 @@ def open(self): print("Could not open socket for {0}".format(self.host)) 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 :type msg: bytes @@ -281,12 +283,11 @@ def _raw(self, msg): self.device.sendall(msg) def _read(self): - """Read data from the TCP socket""" - + """Read data from the TCP socket.""" return self.device.recv(16) def close(self): - """Close TCP connection""" + """Close TCP connection.""" if self.device is not None: try: self.device.shutdown(socket.SHUT_RDWR) @@ -296,7 +297,7 @@ def close(self): 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. 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): - """ + """Initialize file printer with device file. :param devfile: Device file under dev filesystem :param auto_flush: automatically call flush after every call of _raw() @@ -321,18 +322,18 @@ def __init__(self, devfile="/dev/usb/lp0", auto_flush=True, *args, **kwargs): self.open() def open(self): - """Open system file""" + """Open system file.""" self.device = open(self.devfile, "wb") if self.device is None: print("Could not open the specified file {0}".format(self.devfile)) def flush(self): - """Flush printing content""" + """Flush printing content.""" self.device.flush() 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 :type msg: bytes @@ -342,14 +343,14 @@ def _raw(self, msg): self.flush() def close(self): - """Close system file""" + """Close system file.""" if self.device is not None: self.device.flush() self.device.close() class Dummy(Escpos): - """Dummy printer + """Dummy printer. 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 @@ -363,12 +364,12 @@ class Dummy(Escpos): """ def __init__(self, *args, **kwargs): - """ """ + """Init with empty output list.""" Escpos.__init__(self, *args, **kwargs) self._output_list = [] 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 :type msg: bytes @@ -377,11 +378,11 @@ def _raw(self, msg): @property 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) 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 and want to use the Dummy printer for new output. @@ -389,13 +390,20 @@ def clear(self): del self._output_list[:] def close(self): + """Close not implemented for Dummy printer.""" pass if _WIN32PRINT: class Win32Raw(Escpos): + """Printer binding for win32 API. + + Uses the module pywin32 for printing. + """ + def __init__(self, printer_name=None, *args, **kwargs): + """Initialize default printer.""" Escpos.__init__(self, *args, **kwargs) if printer_name is not None: self.printer_name = printer_name @@ -405,6 +413,7 @@ def __init__(self, printer_name=None, *args, **kwargs): self.open() def open(self, job_name="python-escpos"): + """Open connection to default printer.""" if self.printer_name is None: raise Exception("Printer not found") self.hPrinter = win32print.OpenPrinter(self.printer_name) @@ -414,6 +423,7 @@ def open(self, job_name="python-escpos"): win32print.StartPagePrinter(self.hPrinter) def close(self): + """Close connection to default printer.""" if not self.hPrinter: return win32print.EndPagePrinter(self.hPrinter) @@ -422,7 +432,7 @@ def close(self): self.hPrinter = None 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 :type msg: bytes @@ -448,7 +458,7 @@ class CupsPrinter(Escpos): """ def __init__(self, printer_name=None, *args, **kwargs): - """CupsPrinter class constructor. + """Class constructor for CupsPrinter. :param printer_name: CUPS printer name (Optional) :type printer_name: str @@ -477,7 +487,7 @@ def printers(self): return self.conn.getPrinters() 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 the same CUPS connection. @@ -491,7 +501,7 @@ def open(self, job_name="python-escpos"): self.tmpfile = tempfile.NamedTemporaryFile(delete=True) 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 :type msg: bytes @@ -582,7 +592,7 @@ def close(self): self.lp.terminate() def flush(self): - """End line and wait for new commands""" + """End line and wait for new commands.""" if self.lp.stdin.writable(): self.lp.stdin.write(b"\n") if self.lp.stdin.closed is False: diff --git a/test/conftest.py b/test/conftest.py index 2dad0889..c7db0e79 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -1,4 +1,5 @@ import pytest + from escpos.printer import Dummy diff --git a/test/test_abstract_base_class.py b/test/test_abstract_base_class.py index 2e235fda..31544693 100644 --- a/test/test_abstract_base_class.py +++ b/test/test_abstract_base_class.py @@ -1,15 +1,17 @@ #!/usr/bin/python """verifies that the metaclass abc is properly used by ESC/POS -:author: `Patrick Kanzler `_ +:author: `Patrick Kanzler `_ :organization: `python-escpos `_ :copyright: Copyright (c) 2016 Patrick Kanzler :license: MIT """ +from abc import ABCMeta + import pytest + import escpos.escpos as escpos -from abc import ABCMeta def test_abstract_base_class_raises(): diff --git a/test/test_cli.py b/test/test_cli.py index ae763cb7..cd2828ea 100644 --- a/test/test_cli.py +++ b/test/test_cli.py @@ -4,10 +4,12 @@ import os +import shutil +import tempfile + import pytest from scripttest import TestFileEnvironment as TFE -import tempfile -import shutil + import escpos TEST_DIR = tempfile.mkdtemp() + "/cli-test" diff --git a/test/test_function_barcode.py b/test/test_function_barcode.py index b41c5dc2..a7a9b9f8 100644 --- a/test/test_function_barcode.py +++ b/test/test_function_barcode.py @@ -1,10 +1,11 @@ #!/usr/bin/python -import escpos.printer as printer -from escpos.capabilities import Profile, BARCODE_B -from escpos.exceptions import BarcodeTypeError, BarcodeCodeError import pytest +import escpos.printer as printer +from escpos.capabilities import BARCODE_B, Profile +from escpos.exceptions import BarcodeCodeError, BarcodeTypeError + @pytest.mark.parametrize( "bctype,data,expected", diff --git a/test/test_function_buzzer.py b/test/test_function_buzzer.py index a0a8605d..85d5cd73 100644 --- a/test/test_function_buzzer.py +++ b/test/test_function_buzzer.py @@ -1,5 +1,5 @@ -import six import pytest +import six from escpos import printer from escpos.constants import BUZZER diff --git a/test/test_function_cashdraw.py b/test/test_function_cashdraw.py index 95665c69..e976d0d8 100644 --- a/test/test_function_cashdraw.py +++ b/test/test_function_cashdraw.py @@ -1,8 +1,9 @@ #!/usr/bin/python +import pytest + import escpos.printer as printer from escpos.exceptions import CashDrawerError -import pytest def test_raise_CashDrawerError(): diff --git a/test/test_function_check_barcode.py b/test/test_function_check_barcode.py index 6cc9fc95..80e3a962 100644 --- a/test/test_function_check_barcode.py +++ b/test/test_function_check_barcode.py @@ -2,9 +2,10 @@ # -*- coding: utf-8 -*- -import escpos.printer as printer import pytest +import escpos.printer as printer + @pytest.mark.parametrize( "bctype,data", diff --git a/test/test_function_image.py b/test/test_function_image.py index 2f1a3d2a..981d5362 100644 --- a/test/test_function_image.py +++ b/test/test_function_image.py @@ -9,7 +9,6 @@ import pytest - from PIL import Image import escpos.printer as printer diff --git a/test/test_function_linedisplay.py b/test/test_function_linedisplay.py index e9e53ccd..693706ce 100644 --- a/test/test_function_linedisplay.py +++ b/test/test_function_linedisplay.py @@ -1,7 +1,7 @@ #!/usr/bin/python """tests for line display -:author: `Patrick Kanzler `_ +:author: `Patrick Kanzler `_ :organization: `python-escpos `_ :copyright: Copyright (c) 2017 `python-escpos `_ :license: MIT diff --git a/test/test_function_panel_button.py b/test/test_function_panel_button.py index 39529f60..55407718 100644 --- a/test/test_function_panel_button.py +++ b/test/test_function_panel_button.py @@ -1,7 +1,7 @@ #!/usr/bin/python """tests for panel button function -:author: `Patrick Kanzler `_ +:author: `Patrick Kanzler `_ :organization: `python-escpos `_ :copyright: Copyright (c) 2016 `python-escpos `_ :license: MIT diff --git a/test/test_function_qr_non-native.py b/test/test_function_qr_non-native.py index e199e330..f56cc9c0 100644 --- a/test/test_function_qr_non-native.py +++ b/test/test_function_qr_non-native.py @@ -2,18 +2,18 @@ # -*- coding: utf-8 -*- """tests for the non-native part of qr() -:author: `Patrick Kanzler `_ +:author: `Patrick Kanzler `_ :organization: `python-escpos `_ :copyright: Copyright (c) 2016 `python-escpos `_ :license: MIT """ -import pytest import mock +import pytest +from PIL import Image from escpos.printer import Dummy -from PIL import Image @mock.patch("escpos.printer.Dummy.image", spec=Dummy) diff --git a/test/test_function_set.py b/test/test_function_set.py index 40bac640..3eff375d 100644 --- a/test/test_function_set.py +++ b/test/test_function_set.py @@ -1,9 +1,7 @@ import six import escpos.printer as printer -from escpos.constants import TXT_NORMAL, TXT_STYLE, SET_FONT -from escpos.constants import TXT_SIZE - +from escpos.constants import SET_FONT, TXT_NORMAL, TXT_SIZE, TXT_STYLE # Default test, please copy and paste this block to test set method calls diff --git a/test/test_function_softbarcode.py b/test/test_function_softbarcode.py index 0d550240..e3da6853 100644 --- a/test/test_function_softbarcode.py +++ b/test/test_function_softbarcode.py @@ -1,9 +1,10 @@ #!/usr/bin/python -import escpos.printer as printer import barcode.errors import pytest +import escpos.printer as printer + @pytest.fixture def instance(): diff --git a/test/test_function_text.py b/test/test_function_text.py index 83085363..79a2b238 100644 --- a/test/test_function_text.py +++ b/test/test_function_text.py @@ -1,17 +1,18 @@ #!/usr/bin/python """tests for the text printing function -:author: `Patrick Kanzler `_ +:author: `Patrick Kanzler `_ :organization: `python-escpos `_ :copyright: Copyright (c) 2016 `python-escpos `_ :license: MIT """ -import pytest -import mock -from hypothesis import given, assume import hypothesis.strategies as st +import mock +import pytest +from hypothesis import assume, given + from escpos.printer import Dummy diff --git a/test/test_functions.py b/test/test_functions.py index d98336e5..abef814c 100644 --- a/test/test_functions.py +++ b/test/test_functions.py @@ -1,4 +1,5 @@ import pytest + from escpos.printer import Dummy diff --git a/test/test_load_module.py b/test/test_load_module.py index b5daa702..c7c4d54d 100644 --- a/test/test_load_module.py +++ b/test/test_load_module.py @@ -1,7 +1,7 @@ #!/usr/bin/python """very basic test cases that load the classes -:author: `Patrick Kanzler `_ +:author: `Patrick Kanzler `_ :organization: `python-escpos `_ :copyright: Copyright (c) 2016 `python-escpos `_ :license: MIT diff --git a/test/test_magicencode.py b/test/test_magicencode.py index c2e89d13..e7ad2581 100644 --- a/test/test_magicencode.py +++ b/test/test_magicencode.py @@ -2,19 +2,20 @@ # -*- coding: utf-8 -*- """tests for the magic encode module -:author: `Patrick Kanzler `_ +:author: `Patrick Kanzler `_ :organization: `python-escpos `_ :copyright: Copyright (c) 2016 `python-escpos `_ :license: MIT """ -import pytest -from hypothesis import given, example import hypothesis.strategies as st -from escpos.magicencode import MagicEncode, Encoder -from escpos.katakana import encode_katakana +import pytest +from hypothesis import example, given + from escpos.exceptions import CharCodeError, Error +from escpos.katakana import encode_katakana +from escpos.magicencode import Encoder, MagicEncode class TestEncoder: diff --git a/test/test_printer_file.py b/test/test_printer_file.py index b7c89e45..dcda6279 100644 --- a/test/test_printer_file.py +++ b/test/test_printer_file.py @@ -2,16 +2,15 @@ # -*- coding: utf-8 -*- """tests for the File printer -:author: `Patrick Kanzler `_ +:author: `Patrick Kanzler `_ :organization: `python-escpos `_ :copyright: Copyright (c) 2016 `python-escpos `_ :license: MIT """ -import six - import pytest +import six from hypothesis import given, settings from hypothesis.strategies import text diff --git a/test/test_printer_network.py b/test/test_printer_network.py index a1fada12..162d5f6d 100644 --- a/test/test_printer_network.py +++ b/test/test_printer_network.py @@ -1,10 +1,12 @@ #!/usr/bin/python -import escpos.printer as printer -import pytest -import mock import socket +import mock +import pytest + +import escpos.printer as printer + @pytest.fixture def instance(): diff --git a/test/test_profile.py b/test/test_profile.py index 7793cb58..8919dd1f 100644 --- a/test/test_profile.py +++ b/test/test_profile.py @@ -1,5 +1,6 @@ import pytest -from escpos.capabilities import get_profile, NotSupported, BARCODE_B, Profile + +from escpos.capabilities import BARCODE_B, NotSupported, Profile, get_profile @pytest.fixture diff --git a/test/test_raise_arbitrary_error.py b/test/test_raise_arbitrary_error.py index 307f9e37..475dc026 100644 --- a/test/test_raise_arbitrary_error.py +++ b/test/test_raise_arbitrary_error.py @@ -1,7 +1,7 @@ #!/usr/bin/python """test the raising of errors with the error module -:author: `Patrick Kanzler `_ +:author: `Patrick Kanzler `_ :organization: `python-escpos `_ :copyright: Copyright (c) 2017 `python-escpos `_ :license: MIT @@ -9,6 +9,7 @@ import pytest + import escpos import escpos.exceptions diff --git a/test/test_with_statement.py b/test/test_with_statement.py index 8891d128..a4f922ce 100644 --- a/test/test_with_statement.py +++ b/test/test_with_statement.py @@ -1,20 +1,21 @@ #!/usr/bin/python """test the facility which enables usage of the with-statement -:author: `Patrick Kanzler `_ +:author: `Patrick Kanzler `_ :organization: `python-escpos `_ :copyright: Copyright (c) 2016 `python-escpos `_ :license: MIT """ -import escpos.printer as printer import escpos.escpos as escpos +import escpos.printer as printer 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() with escpos.EscposIO(dummy_printer) as p: p.writelines("Some text.\n") - # TODO extend these tests as they don't really do anything at the moment diff --git a/tox.ini b/tox.ini index cfd319d1..9106c370 100644 --- a/tox.ini +++ b/tox.ini @@ -22,8 +22,9 @@ deps = jaconv pytest-mock hypothesis>4 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_* +setenv = PY_IGNORE_IMPORTMISMATCH=1 [testenv:docs] basepython = python @@ -39,7 +40,16 @@ commands = sphinx-build -W -b html -d {envtmpdir}/doctrees . {envtmpdir}/html [testenv:flake8] basepython = python -# TODO add flake8-future -# TODO add flake8-docstrings deps = flake8 + flake8-docstrings commands = flake8 + +[testenv:mypy] +basepython = python +deps = mypy + types-six + types-PyYAML + types-appdirs + types-Pillow + jaconv +commands = mypy src