yeastar-voipms-webhook-proxy/env/Lib/site-packages/phonenumbers/phonenumber.py

278 lines
13 KiB
Python
Raw Normal View History

2025-07-13 22:10:11 -04:00
"""PhoneNumber object definition"""
# Based on original Java code and protocol buffer:
# resources/phonenumber.proto
# java/src/com/google/i18n/phonenumbers/Phonenumber.java
# Copyright (C) 2010-2011 The Libphonenumber Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from .util import UnicodeMixin, ImmutableMixin, mutating_method
from .util import to_long, unicod, rpr, force_unicode, u
class CountryCodeSource(object):
"""The source from which a country code is derived."""
# Default value returned if this is not set, because the phone number was
# created using parse(keep_raw_input=False).
UNSPECIFIED = 0
# The country_code is derived based on a phone number with a leading "+",
# e.g. the French number "+33 1 42 68 53 00".
FROM_NUMBER_WITH_PLUS_SIGN = 1
# The country_code is derived based on a phone number with a leading IDD,
# e.g. the French number "011 33 1 42 68 53 00", as it is dialled
# from US.
FROM_NUMBER_WITH_IDD = 5
# The country_code is derived based on a phone number without a leading
# "+", e.g. the French number "33 1 42 68 53 00" when default_country is
# supplied as France.
FROM_NUMBER_WITHOUT_PLUS_SIGN = 10
# The country_code is derived NOT based on the phone number itself, but
# from the default_country parameter provided in the parsing function by
# the clients. This happens mostly for numbers written in the national
# format (without country code). For example, this would be set when
# parsing the French number "01 42 68 53 00", when default_country is
# supplied as France.
FROM_DEFAULT_COUNTRY = 20
@classmethod
def to_string(cls, val):
"""Return a string representation of a CountryCodeSource value"""
if val == CountryCodeSource.UNSPECIFIED:
return u("UNSPECIFIED")
elif val == CountryCodeSource.FROM_NUMBER_WITH_PLUS_SIGN:
return u("FROM_NUMBER_WITH_PLUS_SIGN")
elif val == CountryCodeSource.FROM_NUMBER_WITH_IDD:
return u("FROM_NUMBER_WITH_IDD")
elif val == CountryCodeSource.FROM_NUMBER_WITHOUT_PLUS_SIGN:
return u("FROM_NUMBER_WITHOUT_PLUS_SIGN")
elif val == CountryCodeSource.FROM_DEFAULT_COUNTRY:
return u("FROM_DEFAULT_COUNTRY")
else:
return u("INVALID (%d)" % val)
class PhoneNumber(UnicodeMixin):
"""Class representing international telephone numbers.
This class is hand-created based on phonenumber.proto. Please refer
to that file for detailed descriptions of the meaning of each field.
"""
def __init__(self,
country_code=None,
national_number=None,
extension=None,
italian_leading_zero=None,
number_of_leading_zeros=None,
raw_input=None,
country_code_source=CountryCodeSource.UNSPECIFIED,
preferred_domestic_carrier_code=None):
# The country calling code for this number, as defined by the
# International Telecommunication Union (ITU). For example, this would
# be 1 for NANPA countries, and 33 for France.
#
# None if not set, of type int otherwise.
if country_code is None:
self.country_code = None
else:
self.country_code = int(country_code)
# Number does not contain National(trunk) prefix.
# National (significant) Number is defined in International
# Telecommunication Union (ITU) Recommendation E.164. It is a
# language/country-neutral representation of a phone number at a
# country level. For countries which have the concept of an "area
# code" or "national destination code", this is included in the
# National (significant) Number. Although the ITU says the maximum
# length should be 15, we have found longer numbers in some countries
# e.g. Germany. Note that the National (significant) Number does not
# contain the National(trunk) prefix.
#
# None if not set, of type long otherwise (and so it will never
# contain any formatting (hypens, spaces, parentheses), nor any
# alphanumeric spellings).
if national_number is None:
self.national_number = None
else:
self.national_number = to_long(national_number)
# Extension is not standardized in ITU recommendations, except for
# being defined as a series of numbers with a maximum length of 40
# digits.
#
# When present, it is a Unicode string to accommodate for the
# possible use of a leading zero in the extension (organizations
# have complete freedom to do so, as there is no standard defined).
# However, only ASCII digits should be stored here.
self.extension = force_unicode(extension) # None or Unicode '[0-9]+'
# In some countries, the national (significant) number starts with one
# or more "0"s without this being a national prefix or trunk code of
# some kind. For example, the leading zero in the national
# (significant) number of an Italian phone number indicates the number
# is a fixed-line number. There have been plans to migrate fixed-line
# numbers to start with the digit two since December 2000, but it has
# not happened yet. See http://en.wikipedia.org/wiki/%2B39 for more
# details.
#
# These fields can be safely ignored (there is no need to set them)
# for most countries. Some limited number of countries behave like
# Italy - for these cases, if the leading zero(s) of a number would be
# retained even when dialling internationally, set this flag to true,
# and also set the number of leading zeros.
#
# Clients who use the parsing functionality of the i18n phone number
# libraries will have these fields set if necessary automatically.
#
# None if not set, of type bool otherwise:
if italian_leading_zero is None:
self.italian_leading_zero = None
else:
self.italian_leading_zero = bool(italian_leading_zero)
# None if not set, of type int otherwise.
if number_of_leading_zeros is None:
self.number_of_leading_zeros = None
else:
self.number_of_leading_zeros = int(number_of_leading_zeros)
# The next few fields are non-essential fields for a phone number.
# They retain extra information about the form the phone number was
# in when it was provided to us to parse. They can be safely
# ignored by most clients.
# This field is used to store the raw input string containing phone
# numbers before it was canonicalized by the library. For example, it
# could be used to store alphanumerical numbers such as
# "1-800-GOOG-411".
self.raw_input = force_unicode(raw_input) # None or Unicode string
# The source from which the country_code is derived. This is not set
# in the general parsing method, but in the method that parses and
# keeps raw_input. New fields could be added upon request.
self.country_code_source = country_code_source # CountryCodeSource.VALUE
if self.country_code_source is None: # pragma no cover
self.country_code_source = CountryCodeSource.UNSPECIFIED
# The carrier selection code that is preferred when calling this
# phone number domestically. This also includes codes that need to
# be dialed in some countries when calling from landlines to mobiles
# or vice versa. For example, in Columbia, a "3" needs to be dialed
# before the phone number itself when calling from a mobile phone to
# a domestic landline phone and vice versa.
#
# Note this is the "preferred" code, which means other codes may work
# as well.
self.preferred_domestic_carrier_code = force_unicode(preferred_domestic_carrier_code)
# None or Unicode string
def clear(self):
"""Erase the contents of the object"""
self.country_code = None
self.national_number = None
self.extension = None
self.italian_leading_zero = None
self.number_of_leading_zeros = None
self.raw_input = None
self.country_code_source = CountryCodeSource.UNSPECIFIED
self.preferred_domestic_carrier_code = None
def merge_from(self, other):
"""Merge information from another PhoneNumber object into this one."""
if other.country_code is not None:
self.country_code = other.country_code
if other.national_number is not None:
self.national_number = other.national_number
if other.extension is not None:
self.extension = other.extension
if other.italian_leading_zero is not None:
self.italian_leading_zero = other.italian_leading_zero
if other.number_of_leading_zeros is not None:
self.number_of_leading_zeros = other.number_of_leading_zeros
if other.raw_input is not None:
self.raw_input = other.raw_input
if other.country_code_source is not CountryCodeSource.UNSPECIFIED:
self.country_code_source = other.country_code_source
if other.preferred_domestic_carrier_code is not None:
self.preferred_domestic_carrier_code = other.preferred_domestic_carrier_code
def __eq__(self, other):
if not isinstance(other, PhoneNumber):
return False
return (self.country_code == other.country_code and
self.national_number == other.national_number and
self.extension == other.extension and
bool(self.italian_leading_zero) == bool(other.italian_leading_zero) and
self.number_of_leading_zeros == other.number_of_leading_zeros and
self.raw_input == other.raw_input and
self.country_code_source == other.country_code_source and
self.preferred_domestic_carrier_code == other.preferred_domestic_carrier_code)
def __ne__(self, other):
return not self.__eq__(other)
def __repr__(self):
return (unicod("%s(country_code=%s, national_number=%s, extension=%s, " +
"italian_leading_zero=%s, number_of_leading_zeros=%s, " +
"country_code_source=%s, preferred_domestic_carrier_code=%s)") %
(type(self).__name__,
self.country_code,
self.national_number,
rpr(self.extension),
self.italian_leading_zero,
self.number_of_leading_zeros,
self.country_code_source,
rpr(self.preferred_domestic_carrier_code)))
def __unicode__(self):
result = (unicod("Country Code: %s National Number: %s") %
(self.country_code, self.national_number))
if self.italian_leading_zero is not None:
result += unicod(" Leading Zero(s): %s") % self.italian_leading_zero
if self.number_of_leading_zeros is not None:
result += unicod(" Number of leading zeros: %d") % self.number_of_leading_zeros
if self.extension is not None:
result += unicod(" Extension: %s") % self.extension
if self.country_code_source is not CountryCodeSource.UNSPECIFIED:
result += unicod(" Country Code Source: %s") % self.country_code_source
if self.preferred_domestic_carrier_code is not None:
result += (unicod(" Preferred Domestic Carrier Code: %s") %
self.preferred_domestic_carrier_code)
return result
class FrozenPhoneNumber(PhoneNumber, ImmutableMixin):
"""Immutable version of PhoneNumber"""
def __hash__(self):
return hash((self.country_code,
self.national_number,
self.extension,
bool(self.italian_leading_zero),
self.number_of_leading_zeros,
self.raw_input,
self.country_code_source,
self.preferred_domestic_carrier_code))
@mutating_method
def __init__(self, *args, **kwargs):
if len(kwargs) == 0 and len(args) == 1 and isinstance(args[0], PhoneNumber):
# Copy constructor
super(FrozenPhoneNumber, self).__init__(**args[0].__dict__)
else:
super(FrozenPhoneNumber, self).__init__(*args, **kwargs)