File tree Expand file tree Collapse file tree
Expand file tree Collapse file tree Original file line number Diff line number Diff line change @@ -62,8 +62,10 @@ Along with being fully configurable to support any arbitrary data source you'd
6262like, the following types of data can immediately be used as configuration formats
6363upon installation of prefer:
6464
65- - JSON
65+ - JSON (with comments)
6666- YAML
67+ - TOML
68+ - INI
6769
6870
6971Why asyncronous?
Original file line number Diff line number Diff line change @@ -113,3 +113,8 @@ def test_get_returns_none_for_unset_nested_identifier():
113113 )
114114 result = subject .get ("level1.nonexistent" )
115115 assert result is None
116+
117+
118+ def test_configuration_using_with_invalid_type_returns_empty ():
119+ result = configuration .Configuration .using ("invalid_string" )
120+ assert result .context == {}
Original file line number Diff line number Diff line change 11defaults = [
22 "prefer.formatters.json:JSONFormatter" ,
33 "prefer.formatters.yaml:YAMLFormatter" ,
4+ "prefer.formatters.ini:INIFormatter" ,
5+ "prefer.formatters.xml:XMLFormatter" ,
46]
Original file line number Diff line number Diff line change 1+ import configparser
2+ import io
3+ import typing
4+
5+ from prefer .formatters import formatter
6+
7+
8+ class INIFormatter (formatter .Formatter ):
9+ @staticmethod
10+ def provides (identifier : str ) -> bool :
11+ return identifier .endswith (".ini" ) or identifier .endswith (".cfg" )
12+
13+ async def serialize (self , source : typing .Dict [str , typing .Any ]) -> str :
14+ config = configparser .ConfigParser ()
15+ for section , values in source .items ():
16+ config [section ] = values
17+ output = io .StringIO ()
18+ config .write (output )
19+ return output .getvalue ()
20+
21+ async def deserialize (self , source : str ) -> typing .Dict [str , typing .Any ]:
22+ config = configparser .ConfigParser ()
23+ config .read_string (source )
24+ result : typing .Dict [str , typing .Any ] = {
25+ section : dict (config [section ]) for section in config .sections ()
26+ }
27+ return result
Original file line number Diff line number Diff line change 1+ import pytest
2+
3+ from prefer .formatters import ini
4+
5+ INI_DATA = """[mock_config]
6+ name = Bailey
7+
8+ """
9+ REAL_DATA = {"mock_config" : {"name" : "Bailey" }}
10+
11+ formatter = ini .INIFormatter ()
12+
13+
14+ @pytest .mark .asyncio
15+ async def test_ini_formatter_provides_expected_file_extensions ():
16+ assert ini .INIFormatter .provides ("test.ini" ) is True
17+ assert ini .INIFormatter .provides ("test.cfg" ) is True
18+
19+
20+ @pytest .mark .asyncio
21+ async def test_ini_formatter_does_not_provide_unexpected_file_extensions ():
22+ assert ini .INIFormatter .provides ("test.json" ) is False
23+ assert ini .INIFormatter .provides ("test.yaml" ) is False
24+
25+
26+ @pytest .mark .asyncio
27+ async def test_ini_formatter_serializes_to_ini ():
28+ result = await formatter .serialize (REAL_DATA )
29+ assert "[mock_config]" in result
30+ assert "name = Bailey" in result
31+
32+
33+ @pytest .mark .asyncio
34+ async def test_ini_formatter_deserializes_from_ini ():
35+ result = await formatter .deserialize (INI_DATA )
36+ assert result == REAL_DATA
Original file line number Diff line number Diff line change 1- import json
21import typing
32
3+ import json5
4+
45from prefer .formatters import formatter
56
67
78class JSONFormatter (formatter .Formatter ):
89 @staticmethod
910 def provides (identifier : str ) -> bool :
10- return identifier .endswith (".json" )
11+ return identifier .endswith (".json" ) or identifier . endswith ( ".json5" )
1112
1213 async def serialize (self , source : typing .Dict [str , typing .Any ]) -> str :
13- return json .dumps (source )
14+ result : str = json5 .dumps (source , quote_keys = True )
15+ return result
1416
1517 async def deserialize (self , source : str ) -> typing .Dict [str , typing .Any ]:
16- result : typing .Dict [str , typing .Any ] = json .loads (source )
18+ result : typing .Dict [str , typing .Any ] = json5 .loads (source )
1719 return result
Original file line number Diff line number Diff line change 1313@pytest .mark .asyncio
1414async def test_json_native_formatter_provides_expected_file_extensions ():
1515 assert json .JSONFormatter .provides ("test.json" ) is True
16+ assert json .JSONFormatter .provides ("test.json5" ) is True
1617
1718
1819@pytest .mark .asyncio
Original file line number Diff line number Diff line change 1+ import typing
2+ import xml .etree .ElementTree as ET
3+
4+ import xmljson
5+
6+ from prefer .formatters import formatter
7+
8+
9+ class XMLFormatter (formatter .Formatter ):
10+ @staticmethod
11+ def provides (identifier : str ) -> bool :
12+ return identifier .endswith (".xml" )
13+
14+ async def serialize (self , source : typing .Dict [str , typing .Any ]) -> str :
15+ elem = xmljson .badgerfish .etree (source )
16+ if isinstance (elem , list ):
17+ elem = elem [0 ]
18+ return ET .tostring (elem , encoding = "unicode" )
19+
20+ async def deserialize (self , source : str ) -> typing .Dict [str , typing .Any ]:
21+ elem = ET .fromstring (source )
22+ result : typing .Dict [str , typing .Any ] = xmljson .badgerfish .data (elem )
23+ return result
Original file line number Diff line number Diff line change 1+ import pytest
2+
3+ from prefer .formatters import xml
4+
5+ XML_DATA = "<root><name>Bailey</name></root>"
6+ REAL_DATA = {"root" : {"name" : {"$" : "Bailey" }}}
7+
8+ formatter = xml .XMLFormatter ()
9+
10+
11+ @pytest .mark .asyncio
12+ async def test_xml_formatter_provides_expected_file_extensions ():
13+ assert xml .XMLFormatter .provides ("test.xml" ) is True
14+
15+
16+ @pytest .mark .asyncio
17+ async def test_xml_formatter_does_not_provide_unexpected_file_extensions ():
18+ assert xml .XMLFormatter .provides ("test.json" ) is False
19+ assert xml .XMLFormatter .provides ("test.yaml" ) is False
20+
21+
22+ @pytest .mark .asyncio
23+ async def test_xml_formatter_serializes_to_xml ():
24+ result = await formatter .serialize (REAL_DATA )
25+ assert "<root>" in result
26+ assert "<name>Bailey</name>" in result
27+
28+
29+ @pytest .mark .asyncio
30+ async def test_xml_formatter_deserializes_from_xml ():
31+ result = await formatter .deserialize (XML_DATA )
32+ assert "root" in result
33+ assert "name" in result ["root" ]
Original file line number Diff line number Diff line change @@ -26,6 +26,7 @@ classifiers = [
2626dependencies = [
2727 " PyYAML>=5.1" ,
2828 " xmljson>=0.2.0" ,
29+ " json5>=0.9.0" ,
2930]
3031
3132[project .optional-dependencies ]
@@ -67,6 +68,10 @@ exclude = [
6768 " .*_spec\\ .py$" ,
6869]
6970
71+ [[tool .mypy .overrides ]]
72+ module = " xmljson"
73+ ignore_missing_imports = true
74+
7075[tool .ruff ]
7176line-length = 79
7277target-version = " py38"
@@ -84,15 +89,7 @@ source = ["prefer"]
8489omit = [" */tests/*" , " */__pycache__/*" , " *_spec.py" ]
8590
8691[tool .coverage .report ]
87- exclude_lines = [
88- " pragma: no cover" ,
89- " def __repr__" ,
90- " raise AssertionError" ,
91- " raise NotImplementedError" ,
92- " if __name__ == .__main__.:" ,
93- " if typing.TYPE_CHECKING:" ,
94- ]
95- fail_under = 99
92+ fail_under = 100
9693show_missing = true
9794
9895[tool .coverage .html ]
You can’t perform that action at this time.
0 commit comments