Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add compatibility with pathlib.Path type from stdlib. #97

Merged
merged 2 commits into from
Apr 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 42 additions & 26 deletions src/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import os
import sys
from importlib.abc import InspectLoader
from pathlib import Path
from types import ModuleType
from typing import Any, Dict, Iterable, List, Mapping, Optional, TextIO, Union, cast

Expand Down Expand Up @@ -349,7 +350,7 @@ class FileConfiguration(Configuration):

def __init__(
self,
data: Union[str, TextIO],
data: Union[str, Path, TextIO],
read_from_file: bool = False,
*,
lowercase_keys: bool = False,
Expand All @@ -370,13 +371,15 @@ def __init__(
interpolate=interpolate,
interpolate_type=interpolate_type,
)
self._filename = data if read_from_file and isinstance(data, str) else None
self._filename = (
data if read_from_file and isinstance(data, (str, Path)) else None
)
self._ignore_missing_paths = ignore_missing_paths
self._reload_with_check(data, read_from_file)

def _reload_with_check(
self,
data: Union[str, TextIO],
data: Union[str, Path, TextIO],
read_from_file: bool = False,
) -> None: # pragma: no cover
try:
Expand All @@ -388,7 +391,7 @@ def _reload_with_check(

def _reload(
self,
data: Union[str, TextIO],
data: Union[str, Path, TextIO],
read_from_file: bool = False,
) -> None: # pragma: no cover
raise NotImplementedError()
Expand All @@ -404,12 +407,12 @@ class JSONConfiguration(FileConfiguration):

def _reload(
self,
data: Union[str, TextIO],
data: Union[str, Path, TextIO],
read_from_file: bool = False,
) -> None:
"""Reload the JSON data."""
if read_from_file:
if isinstance(data, str):
if isinstance(data, (str, Path)):
with open(data, "rt") as f:
result = json.load(f)
else:
Expand All @@ -420,7 +423,7 @@ def _reload(


def config_from_json(
data: Union[str, TextIO],
data: Union[str, Path, TextIO],
read_from_file: bool = False,
*,
lowercase_keys: bool = False,
Expand Down Expand Up @@ -456,7 +459,7 @@ class INIConfiguration(FileConfiguration):

def __init__(
self,
data: Union[str, TextIO],
data: Union[str, Path, TextIO],
read_from_file: bool = False,
*,
section_prefix: str = "",
Expand All @@ -476,7 +479,11 @@ def __init__(
ignore_missing_paths=ignore_missing_paths,
)

def _reload(self, data: Union[str, TextIO], read_from_file: bool = False) -> None:
def _reload(
self,
data: Union[str, Path, TextIO],
read_from_file: bool = False,
) -> None:
"""Reload the INI data."""
import configparser

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

if read_from_file:
if isinstance(data, str):
if isinstance(data, (str, Path)):
with open(data, "rt") as f:
data = f.read()
else:
Expand All @@ -505,7 +512,7 @@ def optionxform(self, optionstr: str) -> str:


def config_from_ini(
data: Union[str, TextIO],
data: Union[str, Path, TextIO],
read_from_file: bool = False,
*,
section_prefix: str = "",
Expand Down Expand Up @@ -543,7 +550,7 @@ class DotEnvConfiguration(FileConfiguration):

def __init__(
self,
data: Union[str, TextIO],
data: Union[str, Path, TextIO],
read_from_file: bool = False,
prefix: str = "",
separator: str = "__",
Expand All @@ -567,12 +574,12 @@ def __init__(

def _reload(
self,
data: Union[str, TextIO],
data: Union[str, Path, TextIO],
read_from_file: bool = False,
) -> None:
"""Reload the .env data."""
if read_from_file:
if isinstance(data, str):
if isinstance(data, (str, Path)):
with open(data, "rt") as f:
data = f.read()
else:
Expand All @@ -594,7 +601,7 @@ def _reload(


def config_from_dotenv(
data: Union[str, TextIO],
data: Union[str, Path, TextIO],
read_from_file: bool = False,
prefix: str = "",
separator: str = "__",
Expand Down Expand Up @@ -634,7 +641,7 @@ class PythonConfiguration(Configuration):

def __init__(
self,
module: Union[str, ModuleType],
module: Union[str, Path, ModuleType],
prefix: str = "",
separator: str = "_",
*,
Expand All @@ -651,7 +658,8 @@ def __init__(
lowercase_keys: whether to convert every key to lower case.
"""
try:
if isinstance(module, str):
if isinstance(module, (str, Path)):
module = str(module)
if module.endswith(".py"):
import importlib.util
from importlib import machinery
Expand Down Expand Up @@ -708,7 +716,7 @@ def reload(self) -> None:


def config_from_python(
module: Union[str, ModuleType],
module: Union[str, Path, ModuleType],
prefix: str = "",
separator: str = "_",
*,
Expand Down Expand Up @@ -796,7 +804,7 @@ class YAMLConfiguration(FileConfiguration):

def __init__(
self,
data: Union[str, TextIO],
data: Union[str, Path, TextIO],
read_from_file: bool = False,
*,
lowercase_keys: bool = False,
Expand All @@ -818,9 +826,13 @@ def __init__(
ignore_missing_paths=ignore_missing_paths,
)

def _reload(self, data: Union[str, TextIO], read_from_file: bool = False) -> None:
def _reload(
self,
data: Union[str, Path, TextIO],
read_from_file: bool = False,
) -> None:
"""Reload the YAML data."""
if read_from_file and isinstance(data, str):
if read_from_file and isinstance(data, (str, Path)):
with open(data, "rt") as f:
loaded = yaml.load(f, Loader=yaml.FullLoader)
else:
Expand All @@ -831,7 +843,7 @@ def _reload(self, data: Union[str, TextIO], read_from_file: bool = False) -> Non


def config_from_yaml(
data: Union[str, TextIO],
data: Union[str, Path, TextIO],
read_from_file: bool = False,
*,
lowercase_keys: bool = False,
Expand Down Expand Up @@ -866,7 +878,7 @@ class TOMLConfiguration(FileConfiguration):

def __init__(
self,
data: Union[str, TextIO],
data: Union[str, Path, TextIO],
read_from_file: bool = False,
*,
section_prefix: str = "",
Expand All @@ -891,10 +903,14 @@ def __init__(
ignore_missing_paths=ignore_missing_paths,
)

def _reload(self, data: Union[str, TextIO], read_from_file: bool = False) -> None:
def _reload(
self,
data: Union[str, Path, TextIO],
read_from_file: bool = False,
) -> None:
"""Reload the TOML data."""
if read_from_file:
if isinstance(data, str):
if isinstance(data, (str, Path)):
with open(data, "rb") as f:
loaded = toml.load(f)
else:
Expand All @@ -914,7 +930,7 @@ def _reload(self, data: Union[str, TextIO], read_from_file: bool = False) -> Non


def config_from_toml(
data: Union[str, TextIO],
data: Union[str, Path, TextIO],
read_from_file: bool = False,
*,
section_prefix: str = "",
Expand Down
13 changes: 12 additions & 1 deletion tests/test_json.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from config import config_from_dict, config_from_json
import tempfile
import json

from pathlib import Path

DICT = {
"a1.b1.c1": 1,
Expand Down Expand Up @@ -49,6 +49,17 @@ def test_load_json_filename(): # type: ignore
assert cfg == config_from_dict(DICT)


def test_load_json_filename_2(): # type: ignore
with tempfile.NamedTemporaryFile() as f:
f.file.write(JSON.encode())
f.file.flush()
cfg = config_from_json(Path(f.name), read_from_file=True)
assert cfg["a1.b1.c1"] == 1
assert cfg["a1.b1"].as_dict() == {"c1": 1, "c2": 2, "c3": 3}
assert cfg["a1.b2"].as_dict() == {"c1": "a", "c2": True, "c3": 1.1}
assert cfg == config_from_dict(DICT)


def test_equality(): # type: ignore
cfg = config_from_json(JSON)
assert cfg == config_from_dict(DICT)
Expand Down
13 changes: 13 additions & 0 deletions tests/test_toml.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from pathlib import Path
import tempfile

import pytest
Expand Down Expand Up @@ -97,6 +98,18 @@ def test_load_toml_filename(): # type: ignore
assert cfg == config_from_dict(DICT)


@pytest.mark.skipif("toml is None")
def test_load_toml_filename_2(): # type: ignore
with tempfile.NamedTemporaryFile() as f:
f.file.write(TOML.encode())
f.file.flush()
cfg = config_from_toml(Path(f.name), read_from_file=True)
assert cfg["a1.b1.c1"] == 1
assert cfg["a1.b1"].as_dict() == {"c1": 1, "c2": 2, "c3": 3}
assert cfg["a1.b2"].as_dict() == {"c1": "a", "c2": True, "c3": 1.1}
assert cfg == config_from_dict(DICT)


@pytest.mark.skipif("toml is None")
def test_equality(): # type: ignore
cfg = config_from_toml(TOML)
Expand Down
13 changes: 13 additions & 0 deletions tests/test_yaml.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import pytest
from config import config_from_dict
from pytest import raises
from pathlib import Path
import tempfile

try:
Expand Down Expand Up @@ -107,6 +108,18 @@ def test_load_yaml_filename(): # type: ignore
assert cfg == config_from_dict(DICT)


@pytest.mark.skipif("yaml is None")
def test_load_yaml_filename_2(): # type: ignore
with tempfile.NamedTemporaryFile() as f:
f.file.write(YAML.encode())
f.file.flush()
cfg = config_from_yaml(Path(f.name), read_from_file=True)
assert cfg["a1.b1.c1"] == 1
assert cfg["a1.b1"].as_dict() == {"c1": 1, "c2": 2, "c3": 3}
assert cfg["a1.b2"].as_dict() == {"c1": "a", "c2": True, "c3": 1.1}
assert cfg == config_from_dict(DICT)


@pytest.mark.skipif("yaml is None")
def test_equality(): # type: ignore
cfg = config_from_yaml(YAML)
Expand Down
Loading