Skip to content

Commit b0fed03

Browse files
authored
add compatibility with pathlib.Path type from stdlib. (#97)
* add compatibility with pathlib.Path type from stdlib. * Fixed tests
1 parent a6adce7 commit b0fed03

File tree

4 files changed

+80
-27
lines changed

4 files changed

+80
-27
lines changed

src/config/__init__.py

Lines changed: 42 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import os
55
import sys
66
from importlib.abc import InspectLoader
7+
from pathlib import Path
78
from types import ModuleType
89
from typing import Any, Dict, Iterable, List, Mapping, Optional, TextIO, Union, cast
910

@@ -349,7 +350,7 @@ class FileConfiguration(Configuration):
349350

350351
def __init__(
351352
self,
352-
data: Union[str, TextIO],
353+
data: Union[str, Path, TextIO],
353354
read_from_file: bool = False,
354355
*,
355356
lowercase_keys: bool = False,
@@ -370,13 +371,15 @@ def __init__(
370371
interpolate=interpolate,
371372
interpolate_type=interpolate_type,
372373
)
373-
self._filename = data if read_from_file and isinstance(data, str) else None
374+
self._filename = (
375+
data if read_from_file and isinstance(data, (str, Path)) else None
376+
)
374377
self._ignore_missing_paths = ignore_missing_paths
375378
self._reload_with_check(data, read_from_file)
376379

377380
def _reload_with_check(
378381
self,
379-
data: Union[str, TextIO],
382+
data: Union[str, Path, TextIO],
380383
read_from_file: bool = False,
381384
) -> None: # pragma: no cover
382385
try:
@@ -388,7 +391,7 @@ def _reload_with_check(
388391

389392
def _reload(
390393
self,
391-
data: Union[str, TextIO],
394+
data: Union[str, Path, TextIO],
392395
read_from_file: bool = False,
393396
) -> None: # pragma: no cover
394397
raise NotImplementedError()
@@ -404,12 +407,12 @@ class JSONConfiguration(FileConfiguration):
404407

405408
def _reload(
406409
self,
407-
data: Union[str, TextIO],
410+
data: Union[str, Path, TextIO],
408411
read_from_file: bool = False,
409412
) -> None:
410413
"""Reload the JSON data."""
411414
if read_from_file:
412-
if isinstance(data, str):
415+
if isinstance(data, (str, Path)):
413416
with open(data, "rt") as f:
414417
result = json.load(f)
415418
else:
@@ -420,7 +423,7 @@ def _reload(
420423

421424

422425
def config_from_json(
423-
data: Union[str, TextIO],
426+
data: Union[str, Path, TextIO],
424427
read_from_file: bool = False,
425428
*,
426429
lowercase_keys: bool = False,
@@ -456,7 +459,7 @@ class INIConfiguration(FileConfiguration):
456459

457460
def __init__(
458461
self,
459-
data: Union[str, TextIO],
462+
data: Union[str, Path, TextIO],
460463
read_from_file: bool = False,
461464
*,
462465
section_prefix: str = "",
@@ -476,7 +479,11 @@ def __init__(
476479
ignore_missing_paths=ignore_missing_paths,
477480
)
478481

479-
def _reload(self, data: Union[str, TextIO], read_from_file: bool = False) -> None:
482+
def _reload(
483+
self,
484+
data: Union[str, Path, TextIO],
485+
read_from_file: bool = False,
486+
) -> None:
480487
"""Reload the INI data."""
481488
import configparser
482489

@@ -487,7 +494,7 @@ def optionxform(self, optionstr: str) -> str:
487494
return super().optionxform(optionstr) if lowercase else optionstr
488495

489496
if read_from_file:
490-
if isinstance(data, str):
497+
if isinstance(data, (str, Path)):
491498
with open(data, "rt") as f:
492499
data = f.read()
493500
else:
@@ -505,7 +512,7 @@ def optionxform(self, optionstr: str) -> str:
505512

506513

507514
def config_from_ini(
508-
data: Union[str, TextIO],
515+
data: Union[str, Path, TextIO],
509516
read_from_file: bool = False,
510517
*,
511518
section_prefix: str = "",
@@ -543,7 +550,7 @@ class DotEnvConfiguration(FileConfiguration):
543550

544551
def __init__(
545552
self,
546-
data: Union[str, TextIO],
553+
data: Union[str, Path, TextIO],
547554
read_from_file: bool = False,
548555
prefix: str = "",
549556
separator: str = "__",
@@ -567,12 +574,12 @@ def __init__(
567574

568575
def _reload(
569576
self,
570-
data: Union[str, TextIO],
577+
data: Union[str, Path, TextIO],
571578
read_from_file: bool = False,
572579
) -> None:
573580
"""Reload the .env data."""
574581
if read_from_file:
575-
if isinstance(data, str):
582+
if isinstance(data, (str, Path)):
576583
with open(data, "rt") as f:
577584
data = f.read()
578585
else:
@@ -594,7 +601,7 @@ def _reload(
594601

595602

596603
def config_from_dotenv(
597-
data: Union[str, TextIO],
604+
data: Union[str, Path, TextIO],
598605
read_from_file: bool = False,
599606
prefix: str = "",
600607
separator: str = "__",
@@ -634,7 +641,7 @@ class PythonConfiguration(Configuration):
634641

635642
def __init__(
636643
self,
637-
module: Union[str, ModuleType],
644+
module: Union[str, Path, ModuleType],
638645
prefix: str = "",
639646
separator: str = "_",
640647
*,
@@ -651,7 +658,8 @@ def __init__(
651658
lowercase_keys: whether to convert every key to lower case.
652659
"""
653660
try:
654-
if isinstance(module, str):
661+
if isinstance(module, (str, Path)):
662+
module = str(module)
655663
if module.endswith(".py"):
656664
import importlib.util
657665
from importlib import machinery
@@ -708,7 +716,7 @@ def reload(self) -> None:
708716

709717

710718
def config_from_python(
711-
module: Union[str, ModuleType],
719+
module: Union[str, Path, ModuleType],
712720
prefix: str = "",
713721
separator: str = "_",
714722
*,
@@ -796,7 +804,7 @@ class YAMLConfiguration(FileConfiguration):
796804

797805
def __init__(
798806
self,
799-
data: Union[str, TextIO],
807+
data: Union[str, Path, TextIO],
800808
read_from_file: bool = False,
801809
*,
802810
lowercase_keys: bool = False,
@@ -818,9 +826,13 @@ def __init__(
818826
ignore_missing_paths=ignore_missing_paths,
819827
)
820828

821-
def _reload(self, data: Union[str, TextIO], read_from_file: bool = False) -> None:
829+
def _reload(
830+
self,
831+
data: Union[str, Path, TextIO],
832+
read_from_file: bool = False,
833+
) -> None:
822834
"""Reload the YAML data."""
823-
if read_from_file and isinstance(data, str):
835+
if read_from_file and isinstance(data, (str, Path)):
824836
with open(data, "rt") as f:
825837
loaded = yaml.load(f, Loader=yaml.FullLoader)
826838
else:
@@ -831,7 +843,7 @@ def _reload(self, data: Union[str, TextIO], read_from_file: bool = False) -> Non
831843

832844

833845
def config_from_yaml(
834-
data: Union[str, TextIO],
846+
data: Union[str, Path, TextIO],
835847
read_from_file: bool = False,
836848
*,
837849
lowercase_keys: bool = False,
@@ -866,7 +878,7 @@ class TOMLConfiguration(FileConfiguration):
866878

867879
def __init__(
868880
self,
869-
data: Union[str, TextIO],
881+
data: Union[str, Path, TextIO],
870882
read_from_file: bool = False,
871883
*,
872884
section_prefix: str = "",
@@ -891,10 +903,14 @@ def __init__(
891903
ignore_missing_paths=ignore_missing_paths,
892904
)
893905

894-
def _reload(self, data: Union[str, TextIO], read_from_file: bool = False) -> None:
906+
def _reload(
907+
self,
908+
data: Union[str, Path, TextIO],
909+
read_from_file: bool = False,
910+
) -> None:
895911
"""Reload the TOML data."""
896912
if read_from_file:
897-
if isinstance(data, str):
913+
if isinstance(data, (str, Path)):
898914
with open(data, "rb") as f:
899915
loaded = toml.load(f)
900916
else:
@@ -914,7 +930,7 @@ def _reload(self, data: Union[str, TextIO], read_from_file: bool = False) -> Non
914930

915931

916932
def config_from_toml(
917-
data: Union[str, TextIO],
933+
data: Union[str, Path, TextIO],
918934
read_from_file: bool = False,
919935
*,
920936
section_prefix: str = "",

tests/test_json.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from config import config_from_dict, config_from_json
22
import tempfile
33
import json
4-
4+
from pathlib import Path
55

66
DICT = {
77
"a1.b1.c1": 1,
@@ -49,6 +49,17 @@ def test_load_json_filename(): # type: ignore
4949
assert cfg == config_from_dict(DICT)
5050

5151

52+
def test_load_json_filename_2(): # type: ignore
53+
with tempfile.NamedTemporaryFile() as f:
54+
f.file.write(JSON.encode())
55+
f.file.flush()
56+
cfg = config_from_json(Path(f.name), read_from_file=True)
57+
assert cfg["a1.b1.c1"] == 1
58+
assert cfg["a1.b1"].as_dict() == {"c1": 1, "c2": 2, "c3": 3}
59+
assert cfg["a1.b2"].as_dict() == {"c1": "a", "c2": True, "c3": 1.1}
60+
assert cfg == config_from_dict(DICT)
61+
62+
5263
def test_equality(): # type: ignore
5364
cfg = config_from_json(JSON)
5465
assert cfg == config_from_dict(DICT)

tests/test_toml.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from pathlib import Path
12
import tempfile
23

34
import pytest
@@ -97,6 +98,18 @@ def test_load_toml_filename(): # type: ignore
9798
assert cfg == config_from_dict(DICT)
9899

99100

101+
@pytest.mark.skipif("toml is None")
102+
def test_load_toml_filename_2(): # type: ignore
103+
with tempfile.NamedTemporaryFile() as f:
104+
f.file.write(TOML.encode())
105+
f.file.flush()
106+
cfg = config_from_toml(Path(f.name), read_from_file=True)
107+
assert cfg["a1.b1.c1"] == 1
108+
assert cfg["a1.b1"].as_dict() == {"c1": 1, "c2": 2, "c3": 3}
109+
assert cfg["a1.b2"].as_dict() == {"c1": "a", "c2": True, "c3": 1.1}
110+
assert cfg == config_from_dict(DICT)
111+
112+
100113
@pytest.mark.skipif("toml is None")
101114
def test_equality(): # type: ignore
102115
cfg = config_from_toml(TOML)

tests/test_yaml.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import pytest
22
from config import config_from_dict
33
from pytest import raises
4+
from pathlib import Path
45
import tempfile
56

67
try:
@@ -107,6 +108,18 @@ def test_load_yaml_filename(): # type: ignore
107108
assert cfg == config_from_dict(DICT)
108109

109110

111+
@pytest.mark.skipif("yaml is None")
112+
def test_load_yaml_filename_2(): # type: ignore
113+
with tempfile.NamedTemporaryFile() as f:
114+
f.file.write(YAML.encode())
115+
f.file.flush()
116+
cfg = config_from_yaml(Path(f.name), read_from_file=True)
117+
assert cfg["a1.b1.c1"] == 1
118+
assert cfg["a1.b1"].as_dict() == {"c1": 1, "c2": 2, "c3": 3}
119+
assert cfg["a1.b2"].as_dict() == {"c1": "a", "c2": True, "c3": 1.1}
120+
assert cfg == config_from_dict(DICT)
121+
122+
110123
@pytest.mark.skipif("yaml is None")
111124
def test_equality(): # type: ignore
112125
cfg = config_from_yaml(YAML)

0 commit comments

Comments
 (0)