Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/phonenumbers/shortnumberinfo.py: 13%

170 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2023-07-17 14:22 -0600

1"""Methods for getting information about short phone numbers, 

2such as short codes and emergency numbers. 

3 

4Note most commercial short numbers are not handled here, but by phonenumberutil.py 

5""" 

6# Based on original Java code: 

7# java/src/com/google/i18n/phonenumbers/ShortNumberInfo.java 

8# Copyright (C) 2013 The Libphonenumber Authors 

9# 

10# Licensed under the Apache License, Version 2.0 (the "License"); 

11# you may not use this file except in compliance with the License. 

12# You may obtain a copy of the License at 

13# 

14# http://www.apache.org/licenses/LICENSE-2.0 

15# 

16# Unless required by applicable law or agreed to in writing, software 

17# distributed under the License is distributed on an "AS IS" BASIS, 

18# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

19# See the License for the specific language governing permissions and 

20# limitations under the License. 

21from .util import U_EMPTY_STRING, prnt 

22from .phonemetadata import PhoneMetadata 

23from .phonenumberutil import _extract_possible_number, _PLUS_CHARS_PATTERN 

24from .phonenumberutil import normalize_digits_only, region_codes_for_country_code 

25from .phonenumberutil import national_significant_number, _match_national_number 

26 

27 

28# Import auto-generated data structures 

29try: 

30 from .shortdata import _AVAILABLE_REGION_CODES as _AVAILABLE_SHORT_REGION_CODES 

31except ImportError: # pragma no cover 

32 # Before the generated code exists, the shortdata/ directory is empty. 

33 # The generation process imports this module, creating a circular 

34 # dependency. The hack below works around this. 

35 import os 

36 import sys 

37 if (os.path.basename(sys.argv[0]) == "buildmetadatafromxml.py" or 

38 os.path.basename(sys.argv[0]) == "buildprefixdata.py"): 

39 prnt("Failed to import generated shortdata (but OK as during autogeneration)", file=sys.stderr) 

40 _AVAILABLE_SHORT_REGION_CODES = [] 

41 else: 

42 raise 

43 

44SUPPORTED_SHORT_REGIONS = _AVAILABLE_SHORT_REGION_CODES 

45 

46# In these countries, if extra digits are added to an emergency number, it no longer connects 

47# to the emergency service. 

48_REGIONS_WHERE_EMERGENCY_NUMBERS_MUST_BE_EXACT = set(["BR", "CL", "NI"]) 

49 

50 

51class ShortNumberCost(object): 

52 """Cost categories of short numbers.""" 

53 TOLL_FREE = 0 

54 STANDARD_RATE = 1 

55 PREMIUM_RATE = 2 

56 UNKNOWN_COST = 3 

57 

58 

59def _region_dialing_from_matches_number(numobj, region_dialing_from): 

60 """Helper method to check that the country calling code of the number matches 

61 the region it's being dialed from.""" 

62 region_codes = region_codes_for_country_code(numobj.country_code) 

63 return (region_dialing_from in region_codes) 

64 

65 

66def is_possible_short_number_for_region(short_numobj, region_dialing_from): 

67 """Check whether a short number is a possible number when dialled from a 

68 region. This provides a more lenient check than 

69 is_valid_short_number_for_region. 

70 

71 Arguments: 

72 short_numobj -- the short number to check as a PhoneNumber object. 

73 region_dialing_from -- the region from which the number is dialed 

74 

75 Return whether the number is a possible short number. 

76 """ 

77 if not _region_dialing_from_matches_number(short_numobj, region_dialing_from): 

78 return False 

79 metadata = PhoneMetadata.short_metadata_for_region(region_dialing_from) 

80 if metadata is None: # pragma no cover 

81 return False 

82 short_numlen = len(national_significant_number(short_numobj)) 

83 return (short_numlen in metadata.general_desc.possible_length) 

84 

85 

86def is_possible_short_number(numobj): 

87 """Check whether a short number is a possible number. 

88 

89 If a country calling code is shared by multiple regions, this returns True 

90 if it's possible in any of them. This provides a more lenient check than 

91 is_valid_short_number. 

92 

93 Arguments: 

94 numobj -- the short number to check 

95 

96 Return whether the number is a possible short number. 

97 """ 

98 region_codes = region_codes_for_country_code(numobj.country_code) 

99 short_number_len = len(national_significant_number(numobj)) 

100 

101 for region in region_codes: 

102 metadata = PhoneMetadata.short_metadata_for_region(region) 

103 if metadata is None: 

104 continue 

105 if short_number_len in metadata.general_desc.possible_length: 

106 return True 

107 return False 

108 

109 

110def is_valid_short_number_for_region(short_numobj, region_dialing_from): 

111 """Tests whether a short number matches a valid pattern in a region. 

112 

113 Note that this doesn't verify the number is actually in use, which is 

114 impossible to tell by just looking at the number itself. 

115 

116 Arguments: 

117 short_numobj -- the short number to check as a PhoneNumber object. 

118 region_dialing_from -- the region from which the number is dialed 

119 

120 Return whether the short number matches a valid pattern 

121 """ 

122 if not _region_dialing_from_matches_number(short_numobj, region_dialing_from): 

123 return False 

124 metadata = PhoneMetadata.short_metadata_for_region(region_dialing_from) 

125 if metadata is None: # pragma no cover 

126 return False 

127 short_number = national_significant_number(short_numobj) 

128 general_desc = metadata.general_desc 

129 if not _matches_possible_number_and_national_number(short_number, general_desc): 

130 return False 

131 short_number_desc = metadata.short_code 

132 if short_number_desc.national_number_pattern is None: # pragma no cover 

133 return False 

134 return _matches_possible_number_and_national_number(short_number, short_number_desc) 

135 

136 

137def is_valid_short_number(numobj): 

138 """Tests whether a short number matches a valid pattern. 

139 

140 If a country calling code is shared by multiple regions, this returns True 

141 if it's valid in any of them. Note that this doesn't verify the number is 

142 actually in use, which is impossible to tell by just looking at the number 

143 itself. See is_valid_short_number_for_region for details. 

144 

145 Arguments: 

146 numobj - the short number for which we want to test the validity 

147 

148 Return whether the short number matches a valid pattern 

149 """ 

150 region_codes = region_codes_for_country_code(numobj.country_code) 

151 region_code = _region_code_for_short_number_from_region_list(numobj, region_codes) 

152 if len(region_codes) > 1 and region_code is not None: 

153 # If a matching region had been found for the phone number from among two or more regions, 

154 # then we have already implicitly verified its validity for that region. 

155 return True 

156 return is_valid_short_number_for_region(numobj, region_code) 

157 

158 

159def expected_cost_for_region(short_numobj, region_dialing_from): 

160 """Gets the expected cost category of a short number when dialled from a 

161 region (however, nothing is implied about its validity). If it is 

162 important that the number is valid, then its validity must first be 

163 checked using is_valid_short_number_for_region. Note that emergency 

164 numbers are always considered toll-free. 

165 

166 Example usage: 

167 short_number = "110" 

168 region_code = "FR" 

169 if phonenumbers.is_valid_short_number_for_region(short_number, region_code): 

170 cost = phonenumbers.expected_cost(short_number, region_code) # ShortNumberCost 

171 # Do something with the cost information here. 

172 

173 Arguments: 

174 short_numobj -- the short number for which we want to know the expected cost category 

175 as a PhoneNumber object. 

176 region_dialing_from -- the region from which the number is dialed 

177 

178 Return the expected cost category for that region of the short 

179 number. Returns UNKNOWN_COST if the number does not match a cost 

180 category. Note that an invalid number may match any cost category. 

181 """ 

182 if not _region_dialing_from_matches_number(short_numobj, region_dialing_from): 

183 return ShortNumberCost.UNKNOWN_COST 

184 # Note that region_dialing_from may be None, in which case metadata will also be None. 

185 metadata = PhoneMetadata.short_metadata_for_region(region_dialing_from) 

186 if metadata is None: # pragma no cover 

187 return ShortNumberCost.UNKNOWN_COST 

188 short_number = national_significant_number(short_numobj) 

189 

190 # The possible lengths are not present for a particular sub-type if they match the general 

191 # description; for this reason, we check the possible lengths against the general description 

192 # first to allow an early exit if possible. 

193 if not (len(short_number) in metadata.general_desc.possible_length): 

194 return ShortNumberCost.UNKNOWN_COST 

195 

196 # The cost categories are tested in order of decreasing expense, since if 

197 # for some reason the patterns overlap the most expensive matching cost 

198 # category should be returned. 

199 if _matches_possible_number_and_national_number(short_number, metadata.premium_rate): 

200 return ShortNumberCost.PREMIUM_RATE 

201 if _matches_possible_number_and_national_number(short_number, metadata.standard_rate): 

202 return ShortNumberCost.STANDARD_RATE 

203 if _matches_possible_number_and_national_number(short_number, metadata.toll_free): 

204 return ShortNumberCost.TOLL_FREE 

205 if is_emergency_number(short_number, region_dialing_from): # pragma no cover 

206 # Emergency numbers are implicitly toll-free. 

207 return ShortNumberCost.TOLL_FREE 

208 return ShortNumberCost.UNKNOWN_COST 

209 

210 

211def expected_cost(numobj): 

212 """Gets the expected cost category of a short number (however, nothing is 

213 implied about its validity). If the country calling code is unique to a 

214 region, this method behaves exactly the same as 

215 expected_cost_for_region. However, if the country calling code is 

216 shared by multiple regions, then it returns the highest cost in the 

217 sequence PREMIUM_RATE, UNKNOWN_COST, STANDARD_RATE, TOLL_FREE. The reason 

218 for the position of UNKNOWN_COST in this order is that if a number is 

219 UNKNOWN_COST in one region but STANDARD_RATE or TOLL_FREE in another, its 

220 expected cost cannot be estimated as one of the latter since it might be a 

221 PREMIUM_RATE number. 

222 

223 For example, if a number is STANDARD_RATE in the US, but TOLL_FREE in 

224 Canada, the expected cost returned by this method will be STANDARD_RATE, 

225 since the NANPA countries share the same country calling code. 

226 

227 Note: If the region from which the number is dialed is known, it is highly preferable to call 

228 expected_cost_for_region instead. 

229 

230 Arguments: 

231 numobj -- the short number for which we want to know the expected cost category 

232 

233 Return the highest expected cost category of the short number in the 

234 region(s) with the given country calling code 

235 """ 

236 region_codes = region_codes_for_country_code(numobj.country_code) 

237 if len(region_codes) == 0: 

238 return ShortNumberCost.UNKNOWN_COST 

239 if len(region_codes) == 1: 

240 return expected_cost_for_region(numobj, region_codes[0]) 

241 cost = ShortNumberCost.TOLL_FREE 

242 for region_code in region_codes: 

243 cost_for_region = expected_cost_for_region(numobj, region_code) 

244 if cost_for_region == ShortNumberCost.PREMIUM_RATE: 

245 return ShortNumberCost.PREMIUM_RATE 

246 elif cost_for_region == ShortNumberCost.UNKNOWN_COST: 

247 return ShortNumberCost.UNKNOWN_COST 

248 elif cost_for_region == ShortNumberCost.STANDARD_RATE: 

249 if cost != ShortNumberCost.UNKNOWN_COST: 

250 cost = ShortNumberCost.STANDARD_RATE 

251 elif cost_for_region == ShortNumberCost.TOLL_FREE: 

252 # Do nothing 

253 pass 

254 else: # pragma no cover 

255 raise Exception("Unrecognized cost for region: %s", cost_for_region) 

256 return cost 

257 

258 

259def _region_code_for_short_number_from_region_list(numobj, region_codes): 

260 """Helper method to get the region code for a given phone number, from a list of possible region 

261 codes. If the list contains more than one region, the first region for which the number is 

262 valid is returned. 

263 """ 

264 if len(region_codes) == 0: 

265 return None 

266 elif len(region_codes) == 1: 

267 return region_codes[0] 

268 national_number = national_significant_number(numobj) 

269 for region_code in region_codes: 

270 metadata = PhoneMetadata.short_metadata_for_region(region_code) 

271 if metadata is not None and _matches_possible_number_and_national_number(national_number, metadata.short_code): 

272 # The number is valid for this region. 

273 return region_code 

274 return None 

275 

276 

277def _example_short_number(region_code): 

278 """Gets a valid short number for the specified region. 

279 

280 Arguments: 

281 region_code -- the region for which an example short number is needed. 

282 

283 Returns a valid short number for the specified region. Returns an empty 

284 string when the metadata does not contain such information. 

285 """ 

286 metadata = PhoneMetadata.short_metadata_for_region(region_code) 

287 if metadata is None: 

288 return U_EMPTY_STRING 

289 desc = metadata.short_code 

290 if desc.example_number is not None: 

291 return desc.example_number 

292 return U_EMPTY_STRING 

293 

294 

295def _example_short_number_for_cost(region_code, cost): 

296 """Gets a valid short number for the specified cost category. 

297 

298 Arguments: 

299 region_code -- the region for which an example short number is needed. 

300 cost -- the cost category of number that is needed. 

301 

302 Returns a valid short number for the specified region and cost 

303 category. Returns an empty string when the metadata does not contain such 

304 information, or the cost is UNKNOWN_COST. 

305 """ 

306 metadata = PhoneMetadata.short_metadata_for_region(region_code) 

307 if metadata is None: 

308 return U_EMPTY_STRING 

309 desc = None 

310 if cost == ShortNumberCost.TOLL_FREE: 

311 desc = metadata.toll_free 

312 elif cost == ShortNumberCost.STANDARD_RATE: 

313 desc = metadata.standard_rate 

314 elif cost == ShortNumberCost.PREMIUM_RATE: 

315 desc = metadata.premium_rate 

316 else: 

317 # ShortNumberCost.UNKNOWN_COST numbers are computed by the process of 

318 # elimination from the other cost categoried. 

319 pass 

320 if desc is not None and desc.example_number is not None: 

321 return desc.example_number 

322 return U_EMPTY_STRING 

323 

324 

325def connects_to_emergency_number(number, region_code): 

326 """Returns whether the given number, exactly as dialled, might be used to 

327 connect to an emergency service in the given region. 

328 

329 This function accepts a string, rather than a PhoneNumber, because it 

330 needs to distinguish cases such as "+1 911" and "911", where the former 

331 may not connect to an emergency service in all cases but the latter would. 

332 

333 This function takes into account cases where the number might contain 

334 formatting, or might have additional digits appended (when it is okay to 

335 do that in the specified region). 

336 

337 Arguments: 

338 number -- The phone number to test. 

339 region_code -- The region where the phone number is being dialed. 

340 

341 Returns whether the number might be used to connect to an emergency 

342 service in the given region. 

343 """ 

344 return _matches_emergency_number_helper(number, region_code, True) # Allows prefix match 

345 

346 

347def is_emergency_number(number, region_code): 

348 """Returns true if the given number exactly matches an emergency service 

349 number in the given region. 

350 

351 This method takes into account cases where the number might contain 

352 formatting, but doesn't allow additional digits to be appended. Note that 

353 is_emergency_number(number, region) implies 

354 connects_to_emergency_number(number, region). 

355 

356 Arguments: 

357 number -- The phone number to test. 

358 region_code -- The region where the phone number is being dialed. 

359 

360 Returns if the number exactly matches an emergency services number in the 

361 given region. 

362 """ 

363 return _matches_emergency_number_helper(number, region_code, False) # Doesn't allow prefix match 

364 

365 

366def _matches_emergency_number_helper(number, region_code, allow_prefix_match): 

367 possible_number = _extract_possible_number(number) 

368 if _PLUS_CHARS_PATTERN.match(possible_number): 

369 # Returns False if the number starts with a plus sign. We don't 

370 # believe dialing the country code before emergency numbers 

371 # (e.g. +1911) works, but later, if that proves to work, we can add 

372 # additional logic here to handle it. 

373 return False 

374 metadata = PhoneMetadata.short_metadata_for_region(region_code.upper(), None) 

375 if metadata is None or metadata.emergency is None: 

376 return False 

377 

378 normalized_number = normalize_digits_only(possible_number) 

379 allow_prefix_match_for_region = (allow_prefix_match and 

380 (region_code not in _REGIONS_WHERE_EMERGENCY_NUMBERS_MUST_BE_EXACT)) 

381 return _match_national_number(normalized_number, metadata.emergency, 

382 allow_prefix_match_for_region) 

383 

384 

385def is_carrier_specific(numobj): 

386 """Given a valid short number, determines whether it is carrier-specific 

387 (however, nothing is implied about its validity). Carrier-specific numbers 

388 may connect to a different end-point, or not connect at all, depending 

389 on the user's carrier. If it is important that the number is valid, then 

390 its validity must first be checked using is_valid_short_number or 

391 is_valid_short_number_for_region. 

392 

393 Arguments: 

394 numobj -- the valid short number to check 

395 

396 Returns whether the short number is carrier-specific, assuming the input 

397 was a valid short number. 

398 """ 

399 region_codes = region_codes_for_country_code(numobj.country_code) 

400 region_code = _region_code_for_short_number_from_region_list(numobj, region_codes) 

401 national_number = national_significant_number(numobj) 

402 metadata = PhoneMetadata.short_metadata_for_region(region_code) 

403 return (metadata is not None and 

404 _matches_possible_number_and_national_number(national_number, metadata.carrier_specific)) 

405 

406 

407def is_carrier_specific_for_region(numobj, region_dialing_from): 

408 """Given a valid short number, determines whether it is carrier-specific when 

409 dialed from the given region (however, nothing is implied about its 

410 validity). Carrier-specific numbers may connect to a different end-point, 

411 or not connect at all, depending on the user's carrier. If it is important 

412 that the number is valid, then its validity must first be checked using 

413 isValidShortNumber or isValidShortNumberForRegion. Returns false if the 

414 number doesn't match the region provided. 

415 

416 Arguments: 

417 numobj -- the valid short number to check 

418 region_dialing_from -- the region from which the number is dialed 

419 

420 Returns whether the short number is carrier-specific, assuming the input 

421 was a valid short number. 

422 """ 

423 if not _region_dialing_from_matches_number(numobj, region_dialing_from): 

424 return False 

425 national_number = national_significant_number(numobj) 

426 metadata = PhoneMetadata.short_metadata_for_region(region_dialing_from) 

427 return (metadata is not None and 

428 _matches_possible_number_and_national_number(national_number, metadata.carrier_specific)) 

429 

430 

431def is_sms_service_for_region(numobj, region_dialing_from): 

432 """Given a valid short number, determines whether it is an SMS service 

433 (however, nothing is implied about its validity). An SMS service is where 

434 the primary or only intended usage is to receive and/or send text messages 

435 (SMSs). This includes MMS as MMS numbers downgrade to SMS if the other 

436 party isn't MMS-capable. If it is important that the number is valid, then 

437 its validity must first be checked using is_valid_short_number or 

438 is_valid_short_number_for_region. Returns False if the number doesn't 

439 match the region provided. 

440 

441 Arguments: 

442 numobj -- the valid short number to check 

443 region_dialing_from -- the region from which the number is dialed 

444 

445 Returns whether the short number is an SMS service in the provided region, 

446 assuming the input was a valid short number. 

447 """ 

448 if not _region_dialing_from_matches_number(numobj, region_dialing_from): 

449 return False 

450 metadata = PhoneMetadata.short_metadata_for_region(region_dialing_from) 

451 return (metadata is not None and 

452 _matches_possible_number_and_national_number(national_significant_number(numobj), metadata.sms_services)) 

453 

454 

455# TODO: Once we have benchmarked ShortNumberInfo, consider if it is worth 

456# keeping this performance optimization. 

457def _matches_possible_number_and_national_number(number, number_desc): 

458 if number_desc is None: 

459 return False 

460 if len(number_desc.possible_length) > 0 and not (len(number) in number_desc.possible_length): 

461 return False 

462 return _match_national_number(number, number_desc, False)