Skip to content

Commit ab7b066

Browse files
Add edge case tests and better INI errors
1 parent dbe106e commit ab7b066

3 files changed

Lines changed: 52 additions & 14 deletions

File tree

src/usethis/_integrations/file/ini/errors.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,15 @@ class INIDecodeError(INIError):
2929

3030
class UnexpectedINIIOError(INIError):
3131
"""Raised when an unexpected attempt is made to read or write the INI file."""
32+
33+
34+
class INIStructureError(INIError):
35+
"""Raised when the INI file has an unexpected structure."""
36+
37+
38+
class InvalidINITypeError(TypeError, INIStructureError):
39+
"""Raised when an invalid type is encountered in the INI file."""
40+
41+
42+
class ININestingError(ValueError, INIStructureError):
43+
"""Raised when there is an unexpected nesting of INI sections."""

src/usethis/_integrations/file/ini/io_.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@
1010

1111
from usethis._integrations.file.ini.errors import (
1212
INIDecodeError,
13+
ININestingError,
1314
ININotFoundError,
1415
INIValueAlreadySetError,
1516
INIValueMissingError,
17+
InvalidINITypeError,
1618
UnexpectedINIIOError,
1719
UnexpectedINIOpenError,
1820
)
@@ -157,7 +159,7 @@ def set_value(
157159
f"INI files do not support nested config, whereas access to "
158160
f"'{self.name}' was attempted at '{'.'.join(keys)}'"
159161
)
160-
raise ValueError(msg)
162+
raise ININestingError(msg)
161163

162164
self.commit(root)
163165

@@ -262,7 +264,7 @@ def _validated_set(
262264
f"INI files only support strings (or lists of strings), but a "
263265
f"{type(value)} was provided."
264266
)
265-
raise NotImplementedError(msg)
267+
raise InvalidINITypeError(msg)
266268

267269
if section_key not in root:
268270
root.add_section(section_key)
@@ -278,7 +280,7 @@ def _validated_append(
278280
f"INI files only support strings (or lists of strings), but a "
279281
f"{type(value)} was provided."
280282
)
281-
raise NotImplementedError(msg)
283+
raise InvalidINITypeError(msg)
282284

283285
if section_key not in root:
284286
root.add_section(section_key)
@@ -335,13 +337,13 @@ def extend_list(self, *, keys: list[str], values: list[str]) -> None:
335337
f"INI files do not support lists at the root level, whereas access to "
336338
f"'{self.name}' was attempted at '{'.'.join(keys)}'"
337339
)
338-
raise ValueError(msg)
340+
raise InvalidINITypeError(msg)
339341
elif len(keys) == 1:
340342
msg = (
341343
f"INI files do not support lists at the section level, whereas access "
342344
f"to '{self.name}' was attempted at '{'.'.join(keys)}'"
343345
)
344-
raise ValueError(msg)
346+
raise InvalidINITypeError(msg)
345347
elif len(keys) == 2:
346348
section_key, option_key = keys
347349
self._extend_list_in_option(
@@ -352,7 +354,7 @@ def extend_list(self, *, keys: list[str], values: list[str]) -> None:
352354
f"INI files do not support nested config, whereas access to "
353355
f"'{self.name}' was attempted at '{'.'.join(keys)}'"
354356
)
355-
raise ValueError(msg)
357+
raise ININestingError(msg)
356358

357359
self.commit(root)
358360

@@ -405,13 +407,13 @@ def remove_from_list(self, *, keys: list[str], values: list[str]) -> None:
405407
f"INI files do not support lists at the root level, whereas access to "
406408
f"'{self.name}' was attempted at '{'.'.join(keys)}'"
407409
)
408-
raise ValueError(msg)
410+
raise InvalidINITypeError(msg)
409411
elif len(keys) == 1:
410412
msg = (
411413
f"INI files do not support lists at the section level, whereas access "
412414
f"to '{self.name}' was attempted at '{'.'.join(keys)}'"
413415
)
414-
raise ValueError(msg)
416+
raise InvalidINITypeError(msg)
415417
elif len(keys) == 2:
416418
section_key, option_key = keys
417419
self._remove_from_list_in_option(
@@ -422,7 +424,7 @@ def remove_from_list(self, *, keys: list[str], values: list[str]) -> None:
422424
f"INI files do not support nested config, whereas access to "
423425
f"'{self.name}' was attempted at '{'.'.join(keys)}'"
424426
)
425-
raise ValueError(msg)
427+
raise ININestingError(msg)
426428

427429
self.commit(root)
428430

tests/usethis/_integrations/file/ini/test_io_.py

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@
66
from usethis._integrations.file.ini.errors import (
77
INIDecodeError,
88
ININotFoundError,
9+
INIStructureError,
910
INIValueAlreadySetError,
1011
INIValueMissingError,
12+
InvalidINITypeError,
1113
UnexpectedINIIOError,
1214
UnexpectedINIOpenError,
1315
)
@@ -563,7 +565,7 @@ def relative_path(self) -> Path:
563565
with (
564566
change_cwd(tmp_path),
565567
MyINIFileManager() as manager,
566-
pytest.raises(NotImplementedError),
568+
pytest.raises(TypeError),
567569
):
568570
manager.set_value(keys=["section"], value={"key": 1}, exists_ok=True)
569571

@@ -1269,7 +1271,8 @@ def relative_path(self) -> Path:
12691271
change_cwd(tmp_path),
12701272
MyINIFileManager() as manager,
12711273
pytest.raises(
1272-
ValueError, match="INI files do not support lists at the root level"
1274+
INIStructureError,
1275+
match="INI files do not support lists at the root level",
12731276
),
12741277
):
12751278
manager.extend_list(keys=[], values=["new_value"])
@@ -1289,7 +1292,7 @@ def relative_path(self) -> Path:
12891292
change_cwd(tmp_path),
12901293
MyINIFileManager() as manager,
12911294
pytest.raises(
1292-
ValueError,
1295+
INIStructureError,
12931296
match="INI files do not support lists at the section level",
12941297
),
12951298
):
@@ -1339,6 +1342,26 @@ def relative_path(self) -> Path:
13391342
"""
13401343
)
13411344

1345+
def test_wrong_list_type_raises(self, tmp_path: Path):
1346+
# Arrange
1347+
class MyINIFileManager(INIFileManager):
1348+
@property
1349+
def relative_path(self) -> Path:
1350+
return Path("valid.ini")
1351+
1352+
valid_file = tmp_path / "valid.ini"
1353+
valid_file.touch()
1354+
1355+
# Act, Assert
1356+
with (
1357+
change_cwd(tmp_path),
1358+
MyINIFileManager() as manager,
1359+
pytest.raises(
1360+
InvalidINITypeError, match="INI files only support strings"
1361+
),
1362+
):
1363+
manager.extend_list(keys=["section", "key"], values=[123]) # type: ignore
1364+
13421365
class TestRemoveFromList:
13431366
def test_singleton_list_collapsed(self, tmp_path: Path):
13441367
# Arrange
@@ -1419,7 +1442,8 @@ def relative_path(self) -> Path:
14191442
change_cwd(tmp_path),
14201443
MyINIFileManager() as manager,
14211444
pytest.raises(
1422-
ValueError, match="INI files do not support lists at the root level"
1445+
INIStructureError,
1446+
match="INI files do not support lists at the root level",
14231447
),
14241448
):
14251449
manager.remove_from_list(keys=[], values=["new_value"])
@@ -1439,7 +1463,7 @@ def relative_path(self) -> Path:
14391463
change_cwd(tmp_path),
14401464
MyINIFileManager() as manager,
14411465
pytest.raises(
1442-
ValueError,
1466+
INIStructureError,
14431467
match="INI files do not support lists at the section level",
14441468
),
14451469
):

0 commit comments

Comments
 (0)