From 42bfe4ad33ce3824d0df60773850170069ccd234 Mon Sep 17 00:00:00 2001 From: Elias0419 Date: Wed, 10 Sep 2025 21:59:07 -0400 Subject: [PATCH 1/9] Support loading yaml strings in Config --- src/escpos/config.py | 61 ++++++++++++++++++++++++++++++-------------- 1 file changed, 42 insertions(+), 19 deletions(-) diff --git a/src/escpos/config.py b/src/escpos/config.py index 7fca8760..9048f70c 100644 --- a/src/escpos/config.py +++ b/src/escpos/config.py @@ -45,6 +45,33 @@ def _reset_config(self) -> None: self._printer_name = None self._printer_config = None + def _set_config(self, config) -> None: + """Set configuration variables. + + :param config: config object loaded from file or string. + """ + if "printer" in config: + self._printer_config = config["printer"] + printer_name = self._printer_config.pop("type") + class_names = { + "usb": "Usb", + "serial": "Serial", + "network": "Network", + "file": "File", + "dummy": "Dummy", + "cupsprinter": "CupsPrinter", + "lp": "LP", + "win32raw": "Win32Raw", + } + self._printer_name = class_names.get(printer_name.lower(), printer_name) + + if not self._printer_name or not hasattr(printer, self._printer_name): + raise exceptions.ConfigSyntaxError( + f'Printer type "{self._printer_name}" is invalid' + ) + + self._has_loaded = True + def load(self, config_path=None): """Load and parse the configuration file using pyyaml. @@ -74,27 +101,23 @@ def load(self, config_path=None): except yaml.YAMLError: raise exceptions.ConfigSyntaxError("Error parsing YAML") - if "printer" in config: - self._printer_config = config["printer"] - printer_name = self._printer_config.pop("type") - class_names = { - "usb": "Usb", - "serial": "Serial", - "network": "Network", - "file": "File", - "dummy": "Dummy", - "cupsprinter": "CupsPrinter", - "lp": "LP", - "win32raw": "Win32Raw", - } - self._printer_name = class_names.get(printer_name.lower(), printer_name) + self._set_config(config) - if not self._printer_name or not hasattr(printer, self._printer_name): - raise exceptions.ConfigSyntaxError( - f'Printer type "{self._printer_name}" is invalid' - ) - self._has_loaded = True + def load_yaml_string(self, yaml_string): + """Load and parse a yaml configuration string. + + :param yaml_string: A string containing yaml formatted configuration + """ + self._reset_config() + + try: + config = yaml.safe_load(yaml_string) + except yaml.YAMLError: + raise exceptions.ConfigSyntaxError("Error parsing YAML") + + self._set_config(config) + def printer(self): """Return a printer that was defined in the config. From 46307b47e6d9e8b68bc18d0d1c76d930346a2305 Mon Sep 17 00:00:00 2001 From: Elias0419 Date: Wed, 10 Sep 2025 22:10:14 -0400 Subject: [PATCH 2/9] tests for loading yaml string --- test/test_config.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/test/test_config.py b/test/test_config.py index 68c8a4b6..94fe312d 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -124,3 +124,29 @@ def test_config_load_with_path(tmp_path): # test the resulting printer object simple_printer_test(c) + +def test_config_load_yaml_string_with_invalid_config_yaml(): + """Invalid YAML string should raise ConfigSyntaxError.""" + from escpos import config + + c = config.Config() + with pytest.raises(escpos.exceptions.ConfigSyntaxError): + c.load_yaml_string("}invalid}yaml}") + + +def test_config_load_yaml_string_with_invalid_config_content(): + """Invalid content should raise ConfigSyntaxError.""" + from escpos import config + + c = config.Config() + with pytest.raises(escpos.exceptions.ConfigSyntaxError): + c.load_yaml_string("printer:\n type: NoPrinterWithThatName\n") + + +def test_config_load_yaml_string_with_valid_config(): + """Valid YAML string should produce a Dummy printer.""" + from escpos import config + + c = config.Config() + c.load_yaml_string("printer:\n type: Dummy\n") + simple_printer_test(c) From bcba002bfe1f47277d54dd2b5bc451b2adaf1d2a Mon Sep 17 00:00:00 2001 From: Elias0419 Date: Sun, 14 Sep 2025 13:09:08 -0400 Subject: [PATCH 3/9] add return type annotations --- src/escpos/config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/escpos/config.py b/src/escpos/config.py index 9048f70c..c1bb8b2f 100644 --- a/src/escpos/config.py +++ b/src/escpos/config.py @@ -72,7 +72,7 @@ def _set_config(self, config) -> None: self._has_loaded = True - def load(self, config_path=None): + def load(self, config_path=None) -> None: """Load and parse the configuration file using pyyaml. :param config_path: An optional file path, file handle, or byte string @@ -104,7 +104,7 @@ def load(self, config_path=None): self._set_config(config) - def load_yaml_string(self, yaml_string): + def load_yaml_string(self, yaml_string) -> None: """Load and parse a yaml configuration string. :param yaml_string: A string containing yaml formatted configuration From 73f15bd91941cf0ccc3405851c7e99c4b48a985c Mon Sep 17 00:00:00 2001 From: Elias0419 Date: Sun, 14 Sep 2025 13:12:55 -0400 Subject: [PATCH 4/9] tests: add return type annotations --- test/test_config.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/test/test_config.py b/test/test_config.py index 94fe312d..37d4026e 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -14,7 +14,7 @@ import escpos.exceptions -def generate_dummy_config(path, content=None): +def generate_dummy_config(path, content=None) -> None: """Generate a dummy config in path""" dummy_config_content = content if not content: @@ -23,7 +23,7 @@ def generate_dummy_config(path, content=None): assert path.read_text() == dummy_config_content -def simple_printer_test(config): +def simple_printer_test(config) -> None: """Simple test for the dummy printer.""" p = config.printer() p._raw(b"1234") @@ -31,7 +31,7 @@ def simple_printer_test(config): assert p.output == b"1234" -def test_config_load_with_invalid_config_yaml(tmp_path): +def test_config_load_with_invalid_config_yaml(tmp_path) -> None: """Test the loading of a config with a invalid config file (yaml issue).""" # generate a dummy config config_file = tmp_path / "config.yaml" @@ -45,7 +45,7 @@ def test_config_load_with_invalid_config_yaml(tmp_path): c.load(config_path=config_file) -def test_config_load_with_invalid_config_content(tmp_path): +def test_config_load_with_invalid_config_content(tmp_path) -> None: """Test the loading of a config with a invalid config file (content issue).""" # generate a dummy config config_file = tmp_path / "config.yaml" @@ -61,7 +61,7 @@ def test_config_load_with_invalid_config_content(tmp_path): c.load(config_path=config_file) -def test_config_load_with_missing_config(tmp_path): +def test_config_load_with_missing_config(tmp_path) -> None: """Test the loading of a config that does not exist.""" # test the config loading from escpos import config @@ -94,7 +94,7 @@ def test_config_load_from_appdir() -> None: simple_printer_test(c) -def test_config_load_with_file(tmp_path): +def test_config_load_with_file(tmp_path) -> None: """Test the loading of a config with a config file.""" # generate a dummy config config_file = tmp_path / "config.yaml" @@ -110,7 +110,7 @@ def test_config_load_with_file(tmp_path): simple_printer_test(c) -def test_config_load_with_path(tmp_path): +def test_config_load_with_path(tmp_path) -> None: """Test the loading of a config with a config path.""" # generate a dummy config config_file = tmp_path / "config.yaml" @@ -125,7 +125,7 @@ def test_config_load_with_path(tmp_path): # test the resulting printer object simple_printer_test(c) -def test_config_load_yaml_string_with_invalid_config_yaml(): +def test_config_load_yaml_string_with_invalid_config_yaml() -> None: """Invalid YAML string should raise ConfigSyntaxError.""" from escpos import config @@ -134,7 +134,7 @@ def test_config_load_yaml_string_with_invalid_config_yaml(): c.load_yaml_string("}invalid}yaml}") -def test_config_load_yaml_string_with_invalid_config_content(): +def test_config_load_yaml_string_with_invalid_config_content() -> None: """Invalid content should raise ConfigSyntaxError.""" from escpos import config @@ -143,7 +143,7 @@ def test_config_load_yaml_string_with_invalid_config_content(): c.load_yaml_string("printer:\n type: NoPrinterWithThatName\n") -def test_config_load_yaml_string_with_valid_config(): +def test_config_load_yaml_string_with_valid_config() -> None: """Valid YAML string should produce a Dummy printer.""" from escpos import config From fe554a43f4ef68f1795afe949d66eb16d2e06e23 Mon Sep 17 00:00:00 2001 From: Elias0419 Date: Sun, 14 Sep 2025 13:49:00 -0400 Subject: [PATCH 5/9] delegate load() to load_yaml_string() --- src/escpos/config.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/escpos/config.py b/src/escpos/config.py index c1bb8b2f..159d983a 100644 --- a/src/escpos/config.py +++ b/src/escpos/config.py @@ -78,7 +78,6 @@ def load(self, config_path=None) -> None: :param config_path: An optional file path, file handle, or byte string for the configuration file. """ - self._reset_config() if not config_path: config_path = os.path.join( @@ -93,16 +92,11 @@ def load(self, config_path=None) -> None: try: with open(config_path, "rb") as config_file: - config = yaml.safe_load(config_file) + self.load_yaml_string(config_file) except EnvironmentError: raise exceptions.ConfigNotFoundError( f"Couldn't read config at {config_path}" ) - except yaml.YAMLError: - raise exceptions.ConfigSyntaxError("Error parsing YAML") - - self._set_config(config) - def load_yaml_string(self, yaml_string) -> None: """Load and parse a yaml configuration string. From b810705129f00e8f9bb82ccaac4ff436c935f354 Mon Sep 17 00:00:00 2001 From: Elias0419 Date: Sun, 14 Sep 2025 13:52:25 -0400 Subject: [PATCH 6/9] update docstrings --- src/escpos/config.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/escpos/config.py b/src/escpos/config.py index 159d983a..776f97c3 100644 --- a/src/escpos/config.py +++ b/src/escpos/config.py @@ -2,6 +2,7 @@ This module contains the implementations of abstract base class :py:class:`Config`. """ + import os import pathlib @@ -99,9 +100,9 @@ def load(self, config_path=None) -> None: ) def load_yaml_string(self, yaml_string) -> None: - """Load and parse a yaml configuration string. + """Load and parse a yaml configuration string or file-like object. - :param yaml_string: A string containing yaml formatted configuration + :param yaml_string: A string or file-like object containing yaml formatted configuration. """ self._reset_config() @@ -112,7 +113,6 @@ def load_yaml_string(self, yaml_string) -> None: self._set_config(config) - def printer(self): """Return a printer that was defined in the config. From ca560e58a9257f67d9da7c600afa5e1853e60f11 Mon Sep 17 00:00:00 2001 From: Elias0419 Date: Sun, 14 Sep 2025 14:12:26 -0400 Subject: [PATCH 7/9] return early if printer isn't found in config --- src/escpos/config.py | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/src/escpos/config.py b/src/escpos/config.py index 776f97c3..78279ca9 100644 --- a/src/escpos/config.py +++ b/src/escpos/config.py @@ -51,25 +51,26 @@ def _set_config(self, config) -> None: :param config: config object loaded from file or string. """ - if "printer" in config: - self._printer_config = config["printer"] - printer_name = self._printer_config.pop("type") - class_names = { - "usb": "Usb", - "serial": "Serial", - "network": "Network", - "file": "File", - "dummy": "Dummy", - "cupsprinter": "CupsPrinter", - "lp": "LP", - "win32raw": "Win32Raw", - } - self._printer_name = class_names.get(printer_name.lower(), printer_name) - - if not self._printer_name or not hasattr(printer, self._printer_name): - raise exceptions.ConfigSyntaxError( - f'Printer type "{self._printer_name}" is invalid' - ) + self._printer_config = config.get("printer") + if self._printer_config is None: + self._has_loaded = True + return + printer_name = self._printer_config.pop("type") + class_names = { + "usb": "Usb", + "serial": "Serial", + "network": "Network", + "file": "File", + "dummy": "Dummy", + "cupsprinter": "CupsPrinter", + "lp": "LP", + "win32raw": "Win32Raw", + } + self._printer_name = class_names.get(printer_name.lower(), printer_name) + if not self._printer_name or not hasattr(printer, self._printer_name): + raise exceptions.ConfigSyntaxError( + f'Printer type "{self._printer_name}" is invalid' + ) self._has_loaded = True From df166d2cb9a48c7b1432e6a7ef438385c5650ae6 Mon Sep 17 00:00:00 2001 From: Elias0419 Date: Mon, 15 Sep 2025 08:37:30 -0400 Subject: [PATCH 8/9] init/reset _printer_name as empty string& guard missing printer --- src/escpos/config.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/escpos/config.py b/src/escpos/config.py index 78279ca9..f04ba5b5 100644 --- a/src/escpos/config.py +++ b/src/escpos/config.py @@ -31,7 +31,7 @@ def __init__(self) -> None: self._has_loaded = False self._printer = None - self._printer_name = None + self._printer_name = "" self._printer_config = None def _reset_config(self) -> None: @@ -42,8 +42,7 @@ def _reset_config(self) -> None: """ self._has_loaded = False self._printer = None - - self._printer_name = None + self._printer_name = "" self._printer_config = None def _set_config(self, config) -> None: @@ -51,11 +50,11 @@ def _set_config(self, config) -> None: :param config: config object loaded from file or string. """ - self._printer_config = config.get("printer") + self._printer_config = config.get("printer", None) if self._printer_config is None: self._has_loaded = True return - printer_name = self._printer_config.pop("type") + printer_name = self._printer_config.get("type", "") class_names = { "usb": "Usb", "serial": "Serial", From a8ebebbc93549e6f303b4e3cf5dea1e6f1df3281 Mon Sep 17 00:00:00 2001 From: Elias0419 Date: Mon, 15 Sep 2025 08:38:06 -0400 Subject: [PATCH 9/9] add cases for empty and missing printer sections --- test/test_config.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/test/test_config.py b/test/test_config.py index 37d4026e..d92ac840 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -125,6 +125,7 @@ def test_config_load_with_path(tmp_path) -> None: # test the resulting printer object simple_printer_test(c) + def test_config_load_yaml_string_with_invalid_config_yaml() -> None: """Invalid YAML string should raise ConfigSyntaxError.""" from escpos import config @@ -150,3 +151,22 @@ def test_config_load_yaml_string_with_valid_config() -> None: c = config.Config() c.load_yaml_string("printer:\n type: Dummy\n") simple_printer_test(c) + + +def test_config_load_with_empty_printer_section() -> None: + """Config with an empty printer section should raise ConfigSyntaxError.""" + from escpos import config + + c = config.Config() + with pytest.raises(escpos.exceptions.ConfigSyntaxError): + c.load_yaml_string("printer: {}") + + +def test_config_load_yaml_string_without_printer_section() -> None: + """Config without a printer section should raise ConfigSectionMissingError.""" + from escpos import config + + c = config.Config() + c.load_yaml_string("{}") + with pytest.raises(escpos.exceptions.ConfigSectionMissingError): + c.printer()