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
« 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.
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
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
44SUPPORTED_SHORT_REGIONS = _AVAILABLE_SHORT_REGION_CODES
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"])
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
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)
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.
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
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)
86def is_possible_short_number(numobj):
87 """Check whether a short number is a possible number.
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.
93 Arguments:
94 numobj -- the short number to check
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))
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
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.
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.
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
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)
137def is_valid_short_number(numobj):
138 """Tests whether a short number matches a valid pattern.
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.
145 Arguments:
146 numobj - the short number for which we want to test the validity
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)
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.
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.
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
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)
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
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
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.
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.
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.
230 Arguments:
231 numobj -- the short number for which we want to know the expected cost category
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
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
277def _example_short_number(region_code):
278 """Gets a valid short number for the specified region.
280 Arguments:
281 region_code -- the region for which an example short number is needed.
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
295def _example_short_number_for_cost(region_code, cost):
296 """Gets a valid short number for the specified cost category.
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.
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
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.
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.
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).
337 Arguments:
338 number -- The phone number to test.
339 region_code -- The region where the phone number is being dialed.
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
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.
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).
356 Arguments:
357 number -- The phone number to test.
358 region_code -- The region where the phone number is being dialed.
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
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
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)
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.
393 Arguments:
394 numobj -- the valid short number to check
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))
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.
416 Arguments:
417 numobj -- the valid short number to check
418 region_dialing_from -- the region from which the number is dialed
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))
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.
441 Arguments:
442 numobj -- the valid short number to check
443 region_dialing_from -- the region from which the number is dialed
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))
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)