diff --git a/src/escpos/config.py b/src/escpos/config.py index 7fca8760..f04ba5b5 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 @@ -30,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: @@ -41,17 +42,43 @@ def _reset_config(self) -> None: """ self._has_loaded = False self._printer = None - - self._printer_name = None + self._printer_name = "" self._printer_config = None - def load(self, config_path=None): + def _set_config(self, config) -> None: + """Set configuration variables. + + :param config: config object loaded from file or string. + """ + self._printer_config = config.get("printer", None) + if self._printer_config is None: + self._has_loaded = True + return + printer_name = self._printer_config.get("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) -> None: """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: config_path = os.path.join( @@ -66,35 +93,25 @@ def load(self, config_path=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}" ) + + def load_yaml_string(self, yaml_string) -> None: + """Load and parse a yaml configuration string or file-like object. + + :param yaml_string: A string or file-like object containing yaml formatted configuration. + """ + self._reset_config() + + try: + config = yaml.safe_load(yaml_string) 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) - - 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 + self._set_config(config) def printer(self): """Return a printer that was defined in the config. diff --git a/test/test_config.py b/test/test_config.py index 68c8a4b6..d92ac840 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" @@ -124,3 +124,49 @@ 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() -> None: + """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() -> None: + """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() -> None: + """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) + + +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()