Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1#!/usr/bin/env python 

2 

3""" 

4camcops_server/cc_modules/cc_formatter.py 

5 

6=============================================================================== 

7 

8 Copyright (C) 2012-2020 Rudolf Cardinal (rudolf@pobox.com). 

9 

10 This file is part of CamCOPS. 

11 

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

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

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

15 (at your option) any later version. 

16 

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

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

19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

20 GNU General Public License for more details. 

21 

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

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

24 

25=============================================================================== 

26 

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

28 allowed keys.** 

29 

30""" 

31 

32from string import Formatter 

33 

34from typing import Any, Mapping, Sequence, Tuple 

35 

36 

37class SafeFormatter(Formatter): 

38 """ 

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

40 of allowed keys. 

41 

42 Basic usage: 

43 

44 .. code-block:: python 

45 

46 from camcops_server.cc_modules.cc_formatter import SafeFormatter 

47 

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

49 

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

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

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

53 """ 

54 

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

56 """ 

57 Args: 

58 allowed_keys: 

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

60 """ 

61 self._allowed_keys = allowed_keys 

62 super().__init__() 

63 

64 def get_valid_parameters_string(self) -> str: 

65 """ 

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

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

68 """ 

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

70 

71 def get_field(self, field_name: str, args: Sequence[Any], 

72 kwargs: Mapping[str, Any]) -> 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)