Skip to content

model.public_ip

model.public_ip

Provide public IP lookup utilities.

Query external services for the host public IP address and validate the result as an IPv4 or IPv6 address.

LOGGER = logging.getLogger(__name__) module-attribute

DEFAULT_TIMEOUT_S = 2.0 module-attribute

USER_AGENT = 'TapMap/1.0 (+https://github.com/olalie/tapmap)' module-attribute

IP_SERVICES = ('https://api.ipify.org', 'https://checkip.amazonaws.com', 'https://ifconfig.me/ip', 'https://icanhazip.com') module-attribute

iter_public_ip_candidates(*, timeout_s=DEFAULT_TIMEOUT_S)

Yield validated public IP addresses from multiple services.

Parameters:

Name Type Description Default
timeout_s float

Request timeout in seconds.

DEFAULT_TIMEOUT_S

Yields:

Type Description
str

Public IP addresses as IPv4 or IPv6 strings.

Source code in model/public_ip.py
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
def iter_public_ip_candidates(*, timeout_s: float = DEFAULT_TIMEOUT_S) -> Iterator[str]:
    """Yield validated public IP addresses from multiple services.

    Args:
        timeout_s: Request timeout in seconds.

    Yields:
        Public IP addresses as IPv4 or IPv6 strings.
    """
    context = ssl.create_default_context(cafile=certifi.where())

    for url in IP_SERVICES:
        request = Request(url, headers={"User-Agent": USER_AGENT})
        try:
            with urlopen(request, timeout=timeout_s, context=context) as resp:
                raw_ip = resp.read().decode("utf-8").strip()
        except URLError as exc:
            LOGGER.debug("Public IP lookup failed for %s: %s", url, exc)
            continue

        try:
            ipaddress.ip_address(raw_ip)
        except ValueError:
            LOGGER.debug("Public IP lookup returned invalid IP from %s: %r", url, raw_ip)
            continue

        yield raw_ip

get_public_ip(*, timeout_s=DEFAULT_TIMEOUT_S)

Return the first validated public IP address, or None.

Source code in model/public_ip.py
61
62
63
def get_public_ip(*, timeout_s: float = DEFAULT_TIMEOUT_S) -> str | None:
    """Return the first validated public IP address, or None."""
    return next(iter_public_ip_candidates(timeout_s=timeout_s), None)