Coverage for cc_modules/cc_formatter.py: 60%

15 statements  

« prev     ^ index     » next       coverage.py v7.9.2, created at 2025-07-15 14:23 +0100

1""" 

2camcops_server/cc_modules/cc_formatter.py 

3 

4=============================================================================== 

5 

6 Copyright (C) 2012, University of Cambridge, Department of Psychiatry. 

7 Created by Rudolf Cardinal (rnc1001@cam.ac.uk). 

8 

9 This file is part of CamCOPS. 

10 

11 CamCOPS is free software: you can redistribute it and/or modify 

12 it under the terms of the GNU General Public License as published by 

13 the Free Software Foundation, either version 3 of the License, or 

14 (at your option) any later version. 

15 

16 CamCOPS is distributed in the hope that it will be useful, 

17 but WITHOUT ANY WARRANTY; without even the implied warranty of 

18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

19 GNU General Public License for more details. 

20 

21 You should have received a copy of the GNU General Public License 

22 along with CamCOPS. If not, see <https://www.gnu.org/licenses/>. 

23 

24=============================================================================== 

25 

26**Safe alternative to str.format() that rejects anything not in the list of 

27allowed keys.** 

28 

29""" 

30 

31from string import Formatter 

32 

33from typing import Any, Mapping, Sequence, Tuple 

34 

35 

36class SafeFormatter(Formatter): 

37 """ 

38 Safe alternative to ``str.format()`` that rejects anything not in the list 

39 of allowed keys. 

40 

41 Basic usage: 

42 

43 .. code-block:: python 

44 

45 from camcops_server.cc_modules.cc_formatter import SafeFormatter 

46 

47 f = SafeFormatter(["a", "b"]) 

48 

49 f.format("a={a}, b={b}", a=1, b=2) # OK 

50 f.format("a={a.__class__}", a=1) # raises KeyError 

51 f.format("a={a}, b={b}, c={c}", a=1, b=2, c=3) # raises KeyError 

52 """ 

53 

54 def __init__(self, allowed_keys: Sequence[str]) -> None: 

55 """ 

56 Args: 

57 allowed_keys: 

58 Keys that are permitted within a brace-delimited format string. 

59 """ 

60 self._allowed_keys = allowed_keys 

61 super().__init__() 

62 

63 def get_valid_parameters_string(self) -> str: 

64 """ 

65 Returns a string, such as ``{a}, {b}``, that enumerates the parameters 

66 allowed (e.g. for user help). 

67 """ 

68 return ", ".join(f"{{{k}}}" for k in self._allowed_keys) 

69 

70 def get_field( 

71 self, field_name: str, args: Sequence[Any], kwargs: Mapping[str, Any] 

72 ) -> Tuple[Any, str]: 

73 """ 

74 Overrides :meth:`Formatter.get_field` (q.v.). 

75 

76 Args: 

77 field_name: 

78 name of the field to be looked up 

79 args: 

80 positional arguments passed to :meth:`format` (not including 

81 the format string) 

82 kwargs: 

83 keyword arguments passed to :meth:`format` 

84 

85 Returns: 

86 tuple: ``(obj, arg_used)`` where ``obj`` is the object that's been 

87 looked up, and ``arg_used`` is the argument it came from 

88 

89 Raises: 

90 - :exc:`KeyError` if the field_name is disallowed 

91 """ 

92 # print(f"field_name={field_name!r}, args={args!r}, kwargs={kwargs!r}") 

93 if field_name not in self._allowed_keys: 

94 raise KeyError(field_name) 

95 

96 return super().get_field(field_name, args, kwargs) 

97 

98 def validate(self, format_string: str) -> None: 

99 """ 

100 Checks a format string for validity. 

101 

102 Args: 

103 format_string: 

104 string to check 

105 

106 Raises: 

107 - :exc:`KeyError` for unknown key 

108 - :exc:`ValueError` for unmatched ``{`` 

109 

110 """ 

111 test_dict = {k: "" for k in self._allowed_keys} 

112 

113 self.format(format_string, **test_dict)