Coverage for flogin/settings.py: 100%

46 statements  

« prev     ^ index     » next       coverage.py v7.9.2, created at 2025-07-03 22:51 +0000

1from __future__ import annotations 

2 

3import logging 

4from typing import TYPE_CHECKING, TypeVar, overload 

5 

6log = logging.getLogger(__name__) 

7 

8if TYPE_CHECKING: 

9 from ._types.json import Jsonable 

10 from ._types.settings import RawSettings 

11 

12T = TypeVar("T") 

13 

14__all__ = ("Settings",) 

15 

16 

17class Settings: 

18 r"""This class represents the settings that you user has chosen. 

19 

20 If a setting is not found, ``None`` is returned instead. 

21 

22 .. container:: operations 

23 

24 .. describe:: x['setting name'] 

25 

26 Get a setting by key similiar to a dictionary 

27 

28 .. describe:: x['setting name', 'default'] 

29 

30 Get a setting by key similiar to a dictionary, with a custom default. 

31 

32 .. describe:: x['setting name'] = "new value" 

33 

34 Change a settings value like a dictionary 

35 

36 .. describe:: x.setting_name 

37 

38 Get a setting by name like an attribute 

39 

40 .. describe:: x.setting_name = "new value" 

41 

42 Change a settings value like an attribute 

43 """ 

44 

45 _data: RawSettings 

46 _changes: RawSettings 

47 

48 def __init__(self, data: RawSettings, *, no_update: bool = False) -> None: 

49 self._data = data 

50 self._changes = {} 

51 self._no_update = no_update 

52 

53 @overload 

54 def __getitem__(self, key: str, /) -> Jsonable: ... 

55 

56 @overload 

57 def __getitem__(self, key: tuple[str, T], /) -> Jsonable | T: ... 

58 

59 def __getitem__(self, key: tuple[str, T] | str) -> Jsonable | T: 

60 if isinstance(key, str): 

61 default = None 

62 else: 

63 key, default = key 

64 return self._data.get(key, default) 

65 

66 def __setitem__(self, key: str, value: Jsonable) -> None: 

67 self._data[key] = value 

68 self._changes[key] = value 

69 

70 def __getattribute__(self, name: str) -> Jsonable: 

71 if name.startswith("_"): 

72 try: 

73 return super().__getattribute__(name) 

74 except AttributeError as e: 

75 raise AttributeError( 

76 f"{e}. Settings that start with an underscore (_) can only be accessed by the __getitem__ method. Ex: settings['_key']" 

77 ) from None 

78 return self.__getitem__(name) 

79 

80 def __setattr__(self, name: str, value: Jsonable) -> None: 

81 if name.startswith("_"): 

82 return super().__setattr__(name, value) 

83 self.__setitem__(name, value) 

84 

85 def _update(self, data: RawSettings) -> None: 

86 if self._no_update: 

87 log.debug("Received a settings update, ignoring. data=%r", data) 

88 else: 

89 log.debug("Updating settings. Before: %s, after: %s", self._data, data) 

90 self._data = data 

91 

92 def _get_updates(self) -> RawSettings: 

93 try: 

94 return self._changes 

95 finally: 

96 log.debug("Resetting setting changes: %s", self._changes) 

97 self._changes = {} 

98 

99 def __repr__(self) -> str: 

100 return f"<Settings current={self._data!r}, pending_changes={self._changes}>"