Skip to content

Commit a6ce40c

Browse files
Elias0419patkan
andauthored
Add Config.load_yaml_string() (#707)
* Support loading yaml strings in Config * tests for loading yaml string * add return type annotations * tests: add return type annotations * delegate load() to load_yaml_string() * update docstrings * return early if printer isn't found in config * init/reset _printer_name as empty string& guard missing printer * add cases for empty and missing printer sections --------- Co-authored-by: Patrick Kanzler <4189642+patkan@users.noreply.github.com>
1 parent 5b8a885 commit a6ce40c

2 files changed

Lines changed: 97 additions & 34 deletions

File tree

src/escpos/config.py

Lines changed: 44 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
33
This module contains the implementations of abstract base class :py:class:`Config`.
44
"""
5+
56
import os
67
import pathlib
78

@@ -30,7 +31,7 @@ def __init__(self) -> None:
3031
self._has_loaded = False
3132
self._printer = None
3233

33-
self._printer_name = None
34+
self._printer_name = ""
3435
self._printer_config = None
3536

3637
def _reset_config(self) -> None:
@@ -41,17 +42,43 @@ def _reset_config(self) -> None:
4142
"""
4243
self._has_loaded = False
4344
self._printer = None
44-
45-
self._printer_name = None
45+
self._printer_name = ""
4646
self._printer_config = None
4747

48-
def load(self, config_path=None):
48+
def _set_config(self, config) -> None:
49+
"""Set configuration variables.
50+
51+
:param config: config object loaded from file or string.
52+
"""
53+
self._printer_config = config.get("printer", None)
54+
if self._printer_config is None:
55+
self._has_loaded = True
56+
return
57+
printer_name = self._printer_config.get("type", "")
58+
class_names = {
59+
"usb": "Usb",
60+
"serial": "Serial",
61+
"network": "Network",
62+
"file": "File",
63+
"dummy": "Dummy",
64+
"cupsprinter": "CupsPrinter",
65+
"lp": "LP",
66+
"win32raw": "Win32Raw",
67+
}
68+
self._printer_name = class_names.get(printer_name.lower(), printer_name)
69+
if not self._printer_name or not hasattr(printer, self._printer_name):
70+
raise exceptions.ConfigSyntaxError(
71+
f'Printer type "{self._printer_name}" is invalid'
72+
)
73+
74+
self._has_loaded = True
75+
76+
def load(self, config_path=None) -> None:
4977
"""Load and parse the configuration file using pyyaml.
5078
5179
:param config_path: An optional file path, file handle, or byte string
5280
for the configuration file.
5381
"""
54-
self._reset_config()
5582

5683
if not config_path:
5784
config_path = os.path.join(
@@ -66,35 +93,25 @@ def load(self, config_path=None):
6693

6794
try:
6895
with open(config_path, "rb") as config_file:
69-
config = yaml.safe_load(config_file)
96+
self.load_yaml_string(config_file)
7097
except EnvironmentError:
7198
raise exceptions.ConfigNotFoundError(
7299
f"Couldn't read config at {config_path}"
73100
)
101+
102+
def load_yaml_string(self, yaml_string) -> None:
103+
"""Load and parse a yaml configuration string or file-like object.
104+
105+
:param yaml_string: A string or file-like object containing yaml formatted configuration.
106+
"""
107+
self._reset_config()
108+
109+
try:
110+
config = yaml.safe_load(yaml_string)
74111
except yaml.YAMLError:
75112
raise exceptions.ConfigSyntaxError("Error parsing YAML")
76113

77-
if "printer" in config:
78-
self._printer_config = config["printer"]
79-
printer_name = self._printer_config.pop("type")
80-
class_names = {
81-
"usb": "Usb",
82-
"serial": "Serial",
83-
"network": "Network",
84-
"file": "File",
85-
"dummy": "Dummy",
86-
"cupsprinter": "CupsPrinter",
87-
"lp": "LP",
88-
"win32raw": "Win32Raw",
89-
}
90-
self._printer_name = class_names.get(printer_name.lower(), printer_name)
91-
92-
if not self._printer_name or not hasattr(printer, self._printer_name):
93-
raise exceptions.ConfigSyntaxError(
94-
f'Printer type "{self._printer_name}" is invalid'
95-
)
96-
97-
self._has_loaded = True
114+
self._set_config(config)
98115

99116
def printer(self):
100117
"""Return a printer that was defined in the config.

test/test_config.py

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
import escpos.exceptions
1515

1616

17-
def generate_dummy_config(path, content=None):
17+
def generate_dummy_config(path, content=None) -> None:
1818
"""Generate a dummy config in path"""
1919
dummy_config_content = content
2020
if not content:
@@ -23,15 +23,15 @@ def generate_dummy_config(path, content=None):
2323
assert path.read_text() == dummy_config_content
2424

2525

26-
def simple_printer_test(config):
26+
def simple_printer_test(config) -> None:
2727
"""Simple test for the dummy printer."""
2828
p = config.printer()
2929
p._raw(b"1234")
3030

3131
assert p.output == b"1234"
3232

3333

34-
def test_config_load_with_invalid_config_yaml(tmp_path):
34+
def test_config_load_with_invalid_config_yaml(tmp_path) -> None:
3535
"""Test the loading of a config with a invalid config file (yaml issue)."""
3636
# generate a dummy config
3737
config_file = tmp_path / "config.yaml"
@@ -45,7 +45,7 @@ def test_config_load_with_invalid_config_yaml(tmp_path):
4545
c.load(config_path=config_file)
4646

4747

48-
def test_config_load_with_invalid_config_content(tmp_path):
48+
def test_config_load_with_invalid_config_content(tmp_path) -> None:
4949
"""Test the loading of a config with a invalid config file (content issue)."""
5050
# generate a dummy config
5151
config_file = tmp_path / "config.yaml"
@@ -61,7 +61,7 @@ def test_config_load_with_invalid_config_content(tmp_path):
6161
c.load(config_path=config_file)
6262

6363

64-
def test_config_load_with_missing_config(tmp_path):
64+
def test_config_load_with_missing_config(tmp_path) -> None:
6565
"""Test the loading of a config that does not exist."""
6666
# test the config loading
6767
from escpos import config
@@ -94,7 +94,7 @@ def test_config_load_from_appdir() -> None:
9494
simple_printer_test(c)
9595

9696

97-
def test_config_load_with_file(tmp_path):
97+
def test_config_load_with_file(tmp_path) -> None:
9898
"""Test the loading of a config with a config file."""
9999
# generate a dummy config
100100
config_file = tmp_path / "config.yaml"
@@ -110,7 +110,7 @@ def test_config_load_with_file(tmp_path):
110110
simple_printer_test(c)
111111

112112

113-
def test_config_load_with_path(tmp_path):
113+
def test_config_load_with_path(tmp_path) -> None:
114114
"""Test the loading of a config with a config path."""
115115
# generate a dummy config
116116
config_file = tmp_path / "config.yaml"
@@ -124,3 +124,49 @@ def test_config_load_with_path(tmp_path):
124124

125125
# test the resulting printer object
126126
simple_printer_test(c)
127+
128+
129+
def test_config_load_yaml_string_with_invalid_config_yaml() -> None:
130+
"""Invalid YAML string should raise ConfigSyntaxError."""
131+
from escpos import config
132+
133+
c = config.Config()
134+
with pytest.raises(escpos.exceptions.ConfigSyntaxError):
135+
c.load_yaml_string("}invalid}yaml}")
136+
137+
138+
def test_config_load_yaml_string_with_invalid_config_content() -> None:
139+
"""Invalid content should raise ConfigSyntaxError."""
140+
from escpos import config
141+
142+
c = config.Config()
143+
with pytest.raises(escpos.exceptions.ConfigSyntaxError):
144+
c.load_yaml_string("printer:\n type: NoPrinterWithThatName\n")
145+
146+
147+
def test_config_load_yaml_string_with_valid_config() -> None:
148+
"""Valid YAML string should produce a Dummy printer."""
149+
from escpos import config
150+
151+
c = config.Config()
152+
c.load_yaml_string("printer:\n type: Dummy\n")
153+
simple_printer_test(c)
154+
155+
156+
def test_config_load_with_empty_printer_section() -> None:
157+
"""Config with an empty printer section should raise ConfigSyntaxError."""
158+
from escpos import config
159+
160+
c = config.Config()
161+
with pytest.raises(escpos.exceptions.ConfigSyntaxError):
162+
c.load_yaml_string("printer: {}")
163+
164+
165+
def test_config_load_yaml_string_without_printer_section() -> None:
166+
"""Config without a printer section should raise ConfigSectionMissingError."""
167+
from escpos import config
168+
169+
c = config.Config()
170+
c.load_yaml_string("{}")
171+
with pytest.raises(escpos.exceptions.ConfigSectionMissingError):
172+
c.printer()

0 commit comments

Comments
 (0)