pyPhoneApi
Async Python library for phone number lookup, validation and formatting.
Built on top of asyncio, aiohttp and phonenumbers. No manually maintained
country/calling-code tables, no blocking calls on the event loop, no synchronous
public API.
Installation
pip install pyPhoneApi
Requirements
- Python >= 3.11
- aiohttp
- phonenumbers
Features
- Fully async public API (
await phone.lookup(...)) - Reuses a single
aiohttp.ClientSessionacross calls, never creates one per request - Country detection, ISO 3166-1 alpha-2 code, calling code, national and
international formatting powered by
phonenumbers - Validity and possibility checks (
is_valid,is_possible) - Number type detection (
MOBILE,FIXED_LINE,VOIP, ...) - Localized country names (
language="en",language="ru", ...) - Typed exceptions instead of generic errors
- Safe under concurrent load with
asyncio.gather - No emoji anywhere in output, logs, or exceptions
- Fully typed, ships with
py.typed
Quick example
import asyncio
from pyPhoneApi import PhoneApi
async def main() -> None:
async with PhoneApi() as phone:
result = await phone.lookup("+380643732627")
print(f"Country: {result.country}")
print(f"Country code: {result.country_code}")
print(f"Calling code: {result.calling_code}")
print(f"Number: {result.number}")
print(f"National: {result.national}")
print(f"Valid: {result.is_valid}")
print(f"Possible: {result.is_possible}")
asyncio.run(main())
Output:
Country: Ukraine
Country code: UA
Calling code: +380
Number: +380643732627
National: 0643732627
Valid: True
Possible: True
Using async with
from pyPhoneApi import PhoneApi
async with PhoneApi() as phone:
result = await phone.lookup("+380643732627")
Manual session lifecycle
from pyPhoneApi import PhoneApi
phone = PhoneApi()
await phone.start()
result = await phone.lookup("+380643732627")
await phone.close()
Repeated calls to lookup() reuse the same aiohttp.ClientSession. Calling
lookup() or start() after close() raises ClientClosedError.
Shortcut function
from pyPhoneApi import lookup
result = await lookup("+380643732627")
Error handling
from pyPhoneApi import PhoneApi
from pyPhoneApi import InvalidPhoneNumberError
async with PhoneApi() as phone:
try:
result = await phone.lookup("invalid-number")
except InvalidPhoneNumberError as e:
print(e)
Output:
Invalid phone number
Concurrent requests
import asyncio
from pyPhoneApi import PhoneApi
async def main() -> None:
async with PhoneApi() as phone:
results = await asyncio.gather(
phone.lookup("+380643732627"),
phone.lookup("+37255555555"),
phone.lookup("+491234567890"),
)
for result in results:
print(result.country, result.number)
asyncio.run(main())
Only one aiohttp.ClientSession is created and shared across all of these
lookups, and the event loop is never blocked.
Localized country names
result = await phone.lookup("+380643732627", language="ru")
print(result.country)
Украина
result = await phone.lookup("+380643732627", language="en")
print(result.country)
Ukraine
PhoneResult
@dataclass(frozen=True, slots=True)
class PhoneResult:
number: str
international: str
national: str
country: str
country_code: str
calling_code: str
is_valid: bool
is_possible: bool
number_type: str
| Field | Description |
|---|---|
number |
Number in E.164 format |
international |
Number in international format |
national |
Number in national format, without separators |
country |
Localized country name |
country_code |
ISO 3166-1 alpha-2 country code (e.g. UA) |
calling_code |
Calling code with leading + (e.g. +380) |
is_valid |
Whether the number is a valid number for its region |
is_possible |
Whether the number is a possible number for its region |
number_type |
MOBILE, FIXED_LINE, FIXED_LINE_OR_MOBILE, TOLL_FREE, PREMIUM_RATE, VOIP, or UNKNOWN |
to_dict():
{
"number": "+380643732627",
"international": "+380643732627",
"national": "0643732627",
"country": "Ukraine",
"country_code": "UA",
"calling_code": "+380",
"is_valid": True,
"is_possible": True,
"number_type": "MOBILE"
}
Exceptions
| Exception | Raised when |
|---|---|
PhoneApiError |
Base exception for all library errors |
InvalidPhoneNumberError |
The number is not a valid or possible phone number |
NumberParseError |
The number could not be parsed at all |
UnsupportedCountryError |
No region could be determined for the number |
ClientClosedError |
lookup() or start() is called after close() |
API reference
class PhoneApi(*, language: str = "en")
await phone.start() -> None— creates the underlyingaiohttp.ClientSessionif one does not already exist.await phone.close() -> None— closes the session and marks the client as closed.await phone.lookup(number: str, *, language: str | None = None) -> PhoneResult— parses and analyzes a phone number.async with PhoneApi() as phone:— starts the session on enter, closes it on exit.phone.is_closed -> bool— whether the client has been closed.
async def lookup(number: str, *, language: str = "en") -> PhoneResult
Shortcut that creates a PhoneApi, performs a single lookup, and closes the
session automatically.
Running tests
pip install -e ".[test]"
pytest
License
MIT
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pyphoneapi-0.1.0.tar.gz.
File metadata
- Download URL: pyphoneapi-0.1.0.tar.gz
- Upload date:
- Size: 5.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.11.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
91ae3a1053332c7695eac87a1d8dbaa6454435ad9af8cbc8fb81faa9210160e6
|
|
| MD5 |
e1fde977b2bcea6ab15928feef573469
|
|
| BLAKE2b-256 |
01f2c3c302453447657d7829f7b6101e825af3e9b762699ac27d7ebaba94ab35
|
File details
Details for the file pyphoneapi-0.1.0-py3-none-any.whl.
File metadata
- Download URL: pyphoneapi-0.1.0-py3-none-any.whl
- Upload date:
- Size: 6.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.11.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c6aa0f1f5706134c04f8c7c772b7a9c3dbae8d0ed81556b8a6812f9bae40f240
|
|
| MD5 |
8d1c31d498adc0e0880e31358c509bfe
|
|
| BLAKE2b-256 |
52fe44f98273b46209e852835c26197e84dbd2d98070448600f5127701579ab0
|