Bases: PropertyType
A phone number in E.164 format. This means that phone numbers always
include an international country prefix (e.g. +38760183628
). The
cleaning and validation functions for this try to be smart about by
accepting a list of countries as an argument in order to add the number
prefix.
When adding a property of this type to an entity, any country-type properties
defined for the entity are considered for validation. That means that adding a
phone number to an entity before adding a country can have a different
validation outcome from doing the two operations the other way around. Always
define the country first.
Source code in followthemoney/types/phone.py
| class PhoneType(PropertyType):
"""A phone number in E.164 format. This means that phone numbers always
include an international country prefix (e.g. `+38760183628`). The
cleaning and validation functions for this try to be smart about by
accepting a list of countries as an argument in order to add the number
prefix.
When adding a property of this type to an entity, any country-type properties
defined for the entity are considered for validation. That means that adding a
phone number to an entity before adding a country can have a different
validation outcome from doing the two operations the other way around. Always
define the country first."""
name = "phone"
group = "phones"
label = _("Phone number")
plural = _("Phone numbers")
matchable = True
pivot = True
max_length = 64
def _clean_countries(
self, proxy: Optional["EntityProxy"]
) -> Iterable[Optional[str]]:
yield None
if proxy is not None:
for country in proxy.countries:
yield country.upper()
def _parse_number(
self, number: str, proxy: Optional["EntityProxy"] = None
) -> Iterable[PhoneNumber]:
"""Parse a phone number and return in international format.
If no valid phone number can be detected, None is returned. If
a country code is supplied, this will be used to infer the
prefix.
https://github.com/daviddrysdale/python-phonenumbers
"""
for code in self._clean_countries(proxy):
try:
yield parse_number(number, code)
except NumberParseException:
pass
def validate(
self, value: str, fuzzy: bool = False, format: Optional[str] = None
) -> bool:
for num in self._parse_number(value):
if is_valid_number(num):
return True
return False
def clean_text(
self,
text: str,
fuzzy: bool = False,
format: Optional[str] = None,
proxy: Optional["EntityProxy"] = None,
) -> Optional[str]:
for num in self._parse_number(text, proxy=proxy):
if is_valid_number(num):
return str(format_number(num, PhoneNumberFormat.E164))
return None
def country_hint(self, value: str) -> Optional[str]:
try:
number = parse_number(value)
code = region_code_for_number(number)
if code is None:
return None
return str(code).lower()
except NumberParseException:
return None
def _specificity(self, value: str) -> float:
# TODO: insert artificial intelligence here.
return dampen(7, 11, value)
def node_id(self, value: str) -> Optional[str]:
return f"tel:{value}"
def caption(self, value: str, format: Optional[str] = None) -> str:
try:
number = parse_number(value)
formatted = format_number(number, PhoneNumberFormat.INTERNATIONAL)
return str(formatted)
except NumberParseException:
return value
|