Coverage for configzen/formats/std_plist.py: 45%

35 statements  

« prev     ^ index     » next       coverage.py v7.5.1, created at 2024-05-15 02:42 +0000

1"""`configzen.formats.std_plist`: The Plist data format.""" 

2 

3from __future__ import annotations 1abcdefgh

4 

5from plistlib import PlistFormat, dump, load 1abcdefgh

6from typing import IO, TYPE_CHECKING, Any, ClassVar 1abcdefgh

7 

8from runtime_generics import runtime_generic 1abcdefgh

9 

10from configzen.data import BinaryDataFormat, DataFormatOptions 1abcdefgh

11 

12if TYPE_CHECKING: 1abcdefgh

13 from collections.abc import MutableMapping 

14 

15 from typing_extensions import Unpack 

16 

17 from configzen.data import Data 

18 

19 

20__all__ = ( 1abcdefgh

21 "PlistDataFormat", 

22 "PlistOptions", 

23) 

24 

25 

26class PlistOptions(DataFormatOptions, total=False): 1abcdefgh

27 """Prototype of the allowed options for the Plist data format.""" 

28 

29 fmt: PlistFormat 1abcdefgh

30 dict_type: type[MutableMapping[str, Any]] 1abcdefgh

31 sort_keys: bool 1abcdefgh

32 skipkeys: bool 1abcdefgh

33 

34 

35@runtime_generic 1abcdefgh

36class PlistDataFormat(BinaryDataFormat[PlistOptions]): 1abcdefgh

37 """The Plist data format.""" 

38 

39 option_name: ClassVar[str] = "plist" 1abcdefgh

40 

41 # Subclass and override for global effect. 

42 plist_options: PlistOptions = PlistOptions() 1abcdefgh

43 

44 default_extension: ClassVar[str] = "plist" 1abcdefgh

45 

46 def configure(self, **options: Unpack[PlistOptions]) -> None: 1abcdefgh

47 """For the documentation of the options, see the PlistOptions class.""" 

48 if "fmt" not in options: 

49 options["fmt"] = self.plist_options.get("fmt", PlistFormat.FMT_XML) 

50 if "dict_type" not in options: 

51 options["dict_type"] = self.plist_options.get( 

52 "dict_type", 

53 dict, 

54 ) # type: ignore[typeddict-item] 

55 if "sort_keys" not in options: 

56 # configzen focuses on preserving the original structure, 

57 # so we don't sort by default. 

58 options["sort_keys"] = self.plist_options.get("sort_keys", False) 

59 if "skipkeys" not in options: 

60 options["skipkeys"] = self.plist_options.get("skipkeys", False) 

61 self.plist_options = options 

62 

63 def load(self, stream: IO[bytes]) -> Data: 1abcdefgh

64 """Load the data from the given stream.""" 

65 dict_class: type[MutableMapping[str, Any]] = self.plist_options["dict_type"] 

66 document = ( 

67 load( 

68 stream, 

69 fmt=self.plist_options["fmt"], 

70 dict_type=dict_class, 

71 ) 

72 or dict_class() 

73 ) 

74 if not isinstance(document, dict_class): 

75 msg = ( 

76 f"Expected a {dict_class.__name__} mapping, " 

77 f"but got {type(document).__name__} instead." 

78 ) 

79 raise TypeError(msg) 

80 return document 

81 

82 def dump(self, data: Data, stream: IO[bytes]) -> None: 1abcdefgh

83 """Dump the given data to the stream.""" 

84 dump( 

85 data, 

86 stream, 

87 fmt=self.plist_options["fmt"], 

88 sort_keys=self.plist_options["sort_keys"], 

89 skipkeys=self.plist_options["skipkeys"], 

90 )