Skip to content

Commit 25db8eb

Browse files
committed
Merge upstream code changes
1 parent c1314d2 commit 25db8eb

6 files changed

Lines changed: 110 additions & 50 deletions

File tree

python/phonenumbers/__init__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@
124124
region_code_for_country_code,
125125
region_codes_for_country_code,
126126
region_code_for_number,
127+
supported_calling_codes,
127128
supported_types_for_region,
128129
supported_types_for_non_geo_entity,
129130
truncate_too_long_number,
@@ -139,7 +140,8 @@
139140
connects_to_emergency_number,
140141
is_emergency_number,
141142
is_carrier_specific,
142-
is_carrier_specific_for_region)
143+
is_carrier_specific_for_region,
144+
is_sms_service_for_region)
143145
from .phonenumbermatcher import PhoneNumberMatch, PhoneNumberMatcher, Leniency
144146

145147

@@ -196,6 +198,7 @@
196198
'region_code_for_country_code',
197199
'region_codes_for_country_code',
198200
'region_code_for_number',
201+
'supported_calling_codes',
199202
'supported_types_for_region',
200203
'supported_types_for_non_geo_entity',
201204
'truncate_too_long_number',
@@ -214,6 +217,7 @@
214217
'is_emergency_number',
215218
'is_carrier_specific',
216219
'is_carrier_specific_for_region',
220+
'is_sms_service_for_region',
217221
# end of items from shortnumberinfo.py
218222
'PhoneNumberMatch', 'PhoneNumberMatcher', 'Leniency',
219223
]

python/phonenumbers/phonenumberutil.py

Lines changed: 41 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,12 @@
33
44
If you use this library, and want to be notified about important changes,
55
please sign up to the libphonenumber mailing list at
6-
http://groups.google.com/group/libphonenumber-discuss/about.
6+
https://groups.google.com/forum/#!aboutgroup/libphonenumber-discuss.
77
88
NOTE: A lot of methods in this module require Region Code strings. These must
99
be provided using CLDR two-letter region-code format. These should be in
1010
upper-case. The list of the codes can be found here:
1111
http://www.iso.org/iso/country_codes/iso_3166_code_lists/country_names_and_code_elements.htm
12-
13-
author: Shaopeng Jia (original Java version)
14-
author: David Drysdale (Python version)
1512
"""
1613
# Based on original Java code:
1714
# java/src/com/google/i18n/phonenumbers/PhoneNumberUtil.java
@@ -186,21 +183,22 @@
186183
[(_c, _c) for _c in _ALPHA_MAPPINGS.keys()],
187184
**_ASCII_DIGITS_MAP))
188185

189-
# Pattern that makes it easy to distinguish whether a region has a unique
190-
# international dialing prefix or not. If a region has a unique international
191-
# prefix (e.g. 011 in USA), it will be represented as a string that contains a
192-
# sequence of ASCII digits. If there are multiple available international
193-
# prefixes in a region, they will be represented as a regex string that always
194-
# contains character(s) other than ASCII digits. Note this regex also
195-
# includes tilde, which signals waiting for the tone.
196-
_UNIQUE_INTERNATIONAL_PREFIX = re.compile(u("[\\d]+(?:[~\u2053\u223C\uFF5E][\\d]+)?"))
186+
# Pattern that makes it easy to distinguish whether a region has a single international dialing
187+
# prefix or not. If a region has a single international prefix (e.g. 011 in USA), it will be
188+
# represented as a string that contains a sequence of ASCII digits, and possibly a tilde, which
189+
# signals waiting for the tone. If there are multiple available international prefixes in a
190+
# region, they will be represented as a regex string that always contains one or more characters
191+
# that are not ASCII digits or a tilde.
192+
_SINGLE_INTERNATIONAL_PREFIX = re.compile(u("[\\d]+(?:[~\u2053\u223C\uFF5E][\\d]+)?"))
197193

198194
# Regular expression of acceptable punctuation found in phone numbers. This
199-
# excludes punctuation found as a leading character only. This consists of
200-
# dash characters, white space characters, full stops, slashes, square
201-
# brackets, parentheses and tildes. It also includes the letter 'x' as that is
202-
# found as a placeholder for carrier information in some phone numbers. Full-width
203-
# variants are also present.
195+
# excludes punctuation found as a leading character only.
196+
197+
# Regular expression of acceptable punctuation found in phone numbers, used to find numbers in
198+
# text and to decide what is a viable phone number. This excludes diallable characters.
199+
# This consists of dash characters, white space characters, full stops, slashes, square brackets,
200+
# parentheses and tildes. It also includes the letter 'x' as that is found as a placeholder for
201+
# carrier information in some phone numbers. Full-width variants are also present.
204202
_VALID_PUNCTUATION = (u("-x\u2010-\u2015\u2212\u30FC\uFF0D-\uFF0F ") +
205203
u("\u00A0\u00AD\u200B\u2060\u3000()\uFF08\uFF09\uFF3B\uFF3D.\\[\\]/~\u2053\u223C\uFF5E"))
206204

@@ -346,9 +344,11 @@ def _create_extn_pattern(single_extn_symbols):
346344
# use \d, so that the first group actually used in the pattern will be
347345
# matched.
348346
_FIRST_GROUP_PATTERN = re.compile(u(r"(\\\d)"))
349-
_NP_PATTERN = re.compile(u("\\$NP"))
350-
_FG_PATTERN = re.compile(u("\\$FG"))
351-
_CC_PATTERN = re.compile(u("\\$CC"))
347+
# Constants used in the formatting rules to represent the national prefix, first group and
348+
# carrier code respectively.
349+
_NP_STRING = "$NP"
350+
_FG_STRING = "$FG"
351+
_CC_STRING = "$CC"
352352

353353
# A pattern that is used to determine if the national prefix formatting rule
354354
# has the first group only, i.e., does not start with the national
@@ -572,7 +572,7 @@ def _normalize(number):
572572
keypad. The keypad used here is the one defined in ITU
573573
Recommendation E.161. This is only done if there are 3 or more
574574
letters in the number, to lessen the risk that such letters are
575-
typos - otherwise alpha characters are stripped.
575+
typos.
576576
- For other numbers:
577577
- Wide-ascii digits are converted to normal ASCII (European) digits.
578578
- Arabic-Indic numerals are converted to European numerals.
@@ -802,7 +802,20 @@ def _normalize_helper(number, replacements, remove_non_matches):
802802
return U_EMPTY_STRING.join(normalized_number)
803803

804804

805+
def supported_calling_codes():
806+
"""Returns all country calling codes the library has metadata for, covering
807+
both non-geographical entities (global network calling codes) and those
808+
used for geographical entities. This could be used to populate a drop-down
809+
box of country calling codes for a phone-number widget, for instance.
810+
811+
Returns an unordered set of the country calling codes for every geographica
812+
and non-geographical entity the library supports.
813+
"""
814+
return set(COUNTRY_CODE_TO_REGION_CODE.keys())
815+
816+
805817
def _desc_has_possible_number_data(desc):
818+
806819
"""Returns true if there is any possible number data set for a particular PhoneNumberDesc."""
807820
# If this is empty, it means numbers of this type inherit from the "general desc" -> the value
808821
# "-1" means that no numbers exist for this type.
@@ -915,7 +928,7 @@ def _is_valid_region_code(region_code):
915928

916929

917930
def _has_valid_country_calling_code(country_calling_code):
918-
return (country_calling_code in _COUNTRY_CODE_TO_REGION_CODE)
931+
return (country_calling_code in COUNTRY_CODE_TO_REGION_CODE)
919932

920933

921934
def format_number(numobj, num_format):
@@ -1019,14 +1032,8 @@ def format_by_pattern(numobj, number_format, user_defined_formats):
10191032
if national_prefix:
10201033
# Replace $NP with national prefix and $FG with the first
10211034
# group (\1) matcher.
1022-
np_formatting_rule = re.sub(_NP_PATTERN,
1023-
national_prefix,
1024-
np_formatting_rule,
1025-
count=1)
1026-
np_formatting_rule = re.sub(_FG_PATTERN,
1027-
unicod("\\\\1"),
1028-
np_formatting_rule,
1029-
count=1)
1035+
np_formatting_rule = np_formatting_rule.replace(_NP_STRING, national_prefix)
1036+
np_formatting_rule = np_formatting_rule.replace(_FG_STRING, unicod("\\1"))
10301037
num_format_copy.national_prefix_formatting_rule = np_formatting_rule
10311038
else:
10321039
# We don't want to have a rule for how to format the national
@@ -1279,7 +1286,7 @@ def format_out_of_country_calling_number(numobj, region_calling_from):
12791286
# format of the number is returned, unless there is a preferred
12801287
# international prefix.
12811288
i18n_prefix_for_formatting = U_EMPTY_STRING
1282-
i18n_match = fullmatch(_UNIQUE_INTERNATIONAL_PREFIX, international_prefix)
1289+
i18n_match = fullmatch(_SINGLE_INTERNATIONAL_PREFIX, international_prefix)
12831290
if i18n_match:
12841291
i18n_prefix_for_formatting = international_prefix
12851292
elif metadata_for_region_calling_from.preferred_international_prefix is not None:
@@ -1517,7 +1524,7 @@ def format_out_of_country_keeping_alpha_chars(numobj, region_calling_from):
15171524
# is returned, unless there is a preferred international prefix.
15181525
if metadata_for_region_calling_from is not None:
15191526
international_prefix = metadata_for_region_calling_from.international_prefix
1520-
i18n_match = fullmatch(_UNIQUE_INTERNATIONAL_PREFIX, international_prefix)
1527+
i18n_match = fullmatch(_SINGLE_INTERNATIONAL_PREFIX, international_prefix)
15211528
if i18n_match:
15221529
i18n_prefix_for_formatting = international_prefix
15231530
else:
@@ -1633,10 +1640,7 @@ def _format_nsn_using_pattern(national_number, formatting_pattern, number_format
16331640
# Replace the $CC in the formatting rule with the desired
16341641
# carrier code.
16351642
cc_format_rule = formatting_pattern.domestic_carrier_code_formatting_rule
1636-
cc_format_rule = re.sub(_CC_PATTERN,
1637-
carrier_code,
1638-
cc_format_rule,
1639-
count=1)
1643+
cc_format_rule = cc_format_rule.replace(_CC_STRING, carrier_code)
16401644

16411645
# Now replace the $FG in the formatting rule with the
16421646
# first group and the carrier code combined in the
@@ -2834,8 +2838,7 @@ def parse(number, region=None, keep_raw_input=False,
28342838
# If no extracted country calling code, use the region supplied
28352839
# instead. The national number is just the normalized version of the
28362840
# number we were given to parse.
2837-
national_number = _normalize(national_number)
2838-
normalized_national_number += national_number
2841+
normalized_national_number += _normalize(national_number)
28392842
if region is not None:
28402843
country_code = metadata.country_code
28412844
numobj.country_code = country_code

python/phonenumbers/shortnumberinfo.py

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -369,8 +369,8 @@ def is_emergency_number(number, region_code):
369369

370370

371371
def _matches_emergency_number_helper(number, region_code, allow_prefix_match):
372-
number = _extract_possible_number(number)
373-
if _PLUS_CHARS_PATTERN.match(number):
372+
possible_number = _extract_possible_number(number)
373+
if _PLUS_CHARS_PATTERN.match(possible_number):
374374
# Returns False if the number starts with a plus sign. We don't
375375
# believe dialing the country code before emergency numbers
376376
# (e.g. +1911) works, but later, if that proves to work, we can add
@@ -380,11 +380,10 @@ def _matches_emergency_number_helper(number, region_code, allow_prefix_match):
380380
if metadata is None or metadata.emergency is None:
381381
return False
382382

383-
normalized_number = normalize_digits_only(number)
384-
emergency_desc = metadata.emergency
383+
normalized_number = normalize_digits_only(possible_number)
385384
allow_prefix_match_for_region = (allow_prefix_match and
386385
(region_code not in _REGIONS_WHERE_EMERGENCY_NUMBERS_MUST_BE_EXACT))
387-
return _match_national_number(normalized_number, emergency_desc,
386+
return _match_national_number(normalized_number, metadata.emergency,
388387
allow_prefix_match_for_region)
389388

390389

@@ -399,8 +398,8 @@ def is_carrier_specific(numobj):
399398
Arguments:
400399
numobj -- the valid short number to check
401400
402-
Returns whether the short number is carrier-specific (assuming the input
403-
was a valid short number).
401+
Returns whether the short number is carrier-specific, assuming the input
402+
was a valid short number.
404403
"""
405404
region_codes = region_codes_for_country_code(numobj.country_code)
406405
region_code = _region_code_for_short_number_from_region_list(numobj, region_codes)
@@ -423,8 +422,8 @@ def is_carrier_specific_for_region(numobj, region_dialing_from):
423422
numobj -- the valid short number to check
424423
region_dialing_from -- the region from which the number is dialed
425424
426-
Returns whether the short number is carrier-specific (assuming the input
427-
was a valid short number).
425+
Returns whether the short number is carrier-specific, assuming the input
426+
was a valid short number.
428427
"""
429428
if not _region_dialing_from_matches_number(numobj, region_dialing_from):
430429
return False
@@ -434,6 +433,30 @@ def is_carrier_specific_for_region(numobj, region_dialing_from):
434433
_matches_possible_number_and_national_number(national_number, metadata.carrier_specific))
435434

436435

436+
def is_sms_service_for_region(numobj, region_dialing_from):
437+
"""Given a valid short number, determines whether it is an SMS service
438+
(however, nothing is implied about its validity). An SMS service is where
439+
the primary or only intended usage is to receive and/or send text messages
440+
(SMSs). This includes MMS as MMS numbers downgrade to SMS if the other
441+
party isn't MMS-capable. If it is important that the number is valid, then
442+
its validity must first be checked using is_valid_short_number or
443+
is_valid_short_number_for_region. Returns False if the number doesn't
444+
match the region provided.
445+
446+
Arguments:
447+
numobj -- the valid short number to check
448+
region_dialing_from -- the region from which the number is dialed
449+
450+
Returns whether the short number is an SMS service in the provided region,
451+
assuming the input was a valid short number.
452+
"""
453+
if not _region_dialing_from_matches_number(numobj, region_dialing_from):
454+
return False
455+
metadata = PhoneMetadata.short_metadata_for_region(region_dialing_from)
456+
return (metadata is not None and
457+
_matches_possible_number_and_national_number(national_significant_number(numobj), metadata.sms_services))
458+
459+
437460
# TODO: Once we have benchmarked ShortNumberInfo, consider if it is worth
438461
# keeping this performance optimization.
439462
def _matches_possible_number_and_national_number(number, number_desc):

python/tests/examplenumberstest.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,6 @@ def testEmergency(self):
221221
def testCarrierSpecificShortNumbers(self):
222222
wrongTagCounter = 0
223223
for regionCode in shortnumberinfo.SUPPORTED_SHORT_REGIONS:
224-
# Test the carrier-specific tag.
225224
metadata = PhoneMetadata.short_metadata_for_region(regionCode, None)
226225
desc = metadata.carrier_specific
227226
if desc is not None and desc.example_number is not None:
@@ -231,7 +230,20 @@ def testCarrierSpecificShortNumbers(self):
231230
not shortnumberinfo.is_carrier_specific_for_region(carrierSpecificNumber, regionCode)):
232231
wrongTagCounter += 1
233232
prnt("Carrier-specific test failed for %s" % regionCode, file=sys.stderr)
234-
# TODO: Test other tags here
233+
self.assertEqual(0, wrongTagCounter)
234+
235+
def testSmsServiceShortNumbers(self):
236+
wrongTagCounter = 0
237+
for regionCode in shortnumberinfo.SUPPORTED_SHORT_REGIONS:
238+
metadata = PhoneMetadata.short_metadata_for_region(regionCode, None)
239+
desc = metadata.sms_services
240+
if desc is not None and desc.example_number is not None:
241+
exampleNumber = desc.example_number
242+
smsServiceNumber = phonenumberutil.parse(exampleNumber, regionCode)
243+
if (not shortnumberinfo.is_possible_short_number_for_region(smsServiceNumber, regionCode) or
244+
not shortnumberinfo.is_sms_service_for_region(smsServiceNumber, regionCode)):
245+
wrongTagCounter += 1
246+
prnt("SMS service test failed for %s" % regionCode, file=sys.stderr)
235247
self.assertEqual(0, wrongTagCounter)
236248

237249
def testIsCarrierSpecific(self):

python/tests/phonenumberutiltest.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,17 @@ def testGetSupportedGlobalNetworkCallingCodes(self):
9696
self.assertTrue(callingCode > 0)
9797
self.assertEqual("001", region_code_for_country_code(callingCode))
9898

99+
def testGetSupportedCallingCodes(self):
100+
callingCodes = phonenumbers.supported_calling_codes()
101+
self.assertTrue(len(callingCodes) > 0)
102+
for callingCode in callingCodes:
103+
self.assertTrue(callingCode > 0)
104+
self.assertTrue(region_code_for_country_code(callingCode) != "ZZ")
105+
# There should be more than just the global network calling codes in this set.
106+
self.assertTrue(len(callingCodes) > len(phonenumbers.COUNTRY_CODES_FOR_NON_GEO_REGIONS))
107+
# But they should be included. Testing one of them.
108+
self.assertTrue(979 in callingCodes)
109+
99110
def testGetSupportedTypesForRegion(self):
100111
self.assertTrue(PhoneNumberType.FIXED_LINE in phonenumbers.supported_types_for_region("BR"))
101112
# Our test data has no mobile numbers for Brazil.

python/tests/shortnumberinfotest.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,13 @@ def testIsCarrierSpecific(self):
8686
# Python version extra test: check invalid region code
8787
self.assertFalse(shortnumberinfo.is_carrier_specific_for_region(carrierSpecificNumberForSomeRegion, "XY"))
8888

89+
def testIsSmsService(self):
90+
smsServiceNumberForSomeRegion = PhoneNumber(country_code=1, national_number=21234)
91+
self.assertTrue(shortnumberinfo.is_sms_service_for_region(smsServiceNumberForSomeRegion, "US"))
92+
self.assertFalse(shortnumberinfo.is_sms_service_for_region(smsServiceNumberForSomeRegion, "BB"))
93+
# Python version extra test: check invalid region code
94+
self.assertFalse(shortnumberinfo.is_sms_service_for_region(smsServiceNumberForSomeRegion, "XY"))
95+
8996
def testGetExpectedCost(self):
9097
premiumRateExample = shortnumberinfo._example_short_number_for_cost("FR", ShortNumberCost.PREMIUM_RATE)
9198
self.assertEqual(ShortNumberCost.PREMIUM_RATE, expected_cost_for_region(_parse(premiumRateExample, "FR"), "FR"))

0 commit comments

Comments
 (0)