Coverage for cc_modules/cc_testproviders.py: 94%

52 statements  

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

1""" 

2camcops_server/cc_modules/cc_testproviders.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**Faker test data providers.** 

27 

28There may be some interest in a Faker Medical community provider if we felt it 

29was worth the effort. 

30 

31https://github.com/joke2k/faker/issues/1142 

32 

33See also duplicate functionality in CRATE: crate_anon/testing/providers.py 

34 

35""" 

36 

37import datetime 

38 

39from cardinal_pythonlib.nhs import generate_random_nhs_number 

40from faker import Faker 

41from faker.providers import BaseProvider 

42import pendulum 

43from pendulum import DateTime as Pendulum 

44from typing import Any, List 

45 

46 

47class NhsNumberProvider(BaseProvider): 

48 @staticmethod 

49 def nhs_number() -> int: 

50 return generate_random_nhs_number() 

51 

52 

53class ChoiceProvider(BaseProvider): 

54 def random_choice(self, choices: List, **kwargs: Any) -> Any: 

55 """ 

56 Given a list of choices return a random value 

57 """ 

58 choices = self.generator.random.choices(choices, **kwargs) 

59 

60 return choices[0] 

61 

62 

63# No one is born after this 

64_max_birth_datetime = Pendulum(year=2000, month=1, day=1, hour=9) 

65 

66 

67class ConsistentDateOfBirthProvider(BaseProvider): 

68 """ 

69 Faker date_of_birth calculates from the current time so gives different 

70 results on different days. 

71 """ 

72 

73 def consistent_date_of_birth(self) -> datetime.datetime: 

74 return self.generator.date_between_dates( 

75 date_start=pendulum.date(1900, 1, 1), 

76 date_end=_max_birth_datetime, 

77 ) 

78 

79 

80class ForenameProvider(BaseProvider): 

81 """ 

82 Return a forename given the sex of the person 

83 """ 

84 

85 def forename(self, sex: str) -> str: 

86 if sex == "M": 

87 return self.generator.first_name_male() 

88 

89 if sex == "F": 

90 return self.generator.first_name_female() 

91 

92 return self.generator.first_name()[:1] 

93 

94 

95class HeightProvider(BaseProvider): 

96 def height_m(self) -> float: 

97 """ 

98 Return a random patient height in metres 

99 """ 

100 

101 return float(self.generator.random_int(min=145, max=191) / 100.0) 

102 

103 

104class MassProvider(BaseProvider): 

105 def mass_kg(self) -> float: 

106 """ 

107 Return a random patient mass in kilograms 

108 """ 

109 

110 return float(self.generator.random_int(min=400, max=1000) / 10.0) 

111 

112 

113class SexProvider(ChoiceProvider): 

114 """ 

115 Return a random sex, with realistic distribution. 

116 """ 

117 

118 def sex(self) -> str: 

119 return self.random_choice(["M", "F", "X"], weights=[49.8, 49.8, 0.4]) 

120 

121 

122class ValidPhoneNumberProvider(BaseProvider): 

123 """ 

124 Return a random mobile phone number 

125 """ 

126 

127 # The default Faker phone_number provider for en_GB uses 

128 # https://www.ofcom.org.uk/phones-telecoms-and-internet/information-for-industry/numbering/numbers-for-drama # noqa: E501 

129 # 07700 900000 to 900999 reserved for TV and Radio drama purposes 

130 # but unfortunately the phonenumbers library considers these invalid. 

131 def valid_phone_number(self) -> str: 

132 number = self.generator.random_int(min=7000000000, max=7999999999) 

133 

134 return f"+44{number}" 

135 

136 

137class WaistProvider(BaseProvider): 

138 """ 

139 Return a random waist circumference in centimetres 

140 """ 

141 

142 def waist_cm(self) -> float: 

143 return float(self.generator.random_int(min=40, max=130)) 

144 

145 

146def register_all_providers(fake: Faker) -> None: 

147 fake.add_provider(ChoiceProvider) 

148 fake.add_provider(ConsistentDateOfBirthProvider) 

149 fake.add_provider(ForenameProvider) 

150 fake.add_provider(HeightProvider) 

151 fake.add_provider(MassProvider) 

152 fake.add_provider(NhsNumberProvider) 

153 fake.add_provider(ValidPhoneNumberProvider) 

154 fake.add_provider(WaistProvider) 

155 fake.add_provider(SexProvider)