Fast, offline IPv4/IPv6 to ISO 3166-1 alpha-2 country code lookup. One Rust core with Python, WebAssembly, and CLI bindings.
Important
iptocc 3.0 is a complete Rust rewrite. Previous versions (2.x and earlier) were pure-Python on top of pandas. The 3.x line ships a Rust core with PyO3 Python bindings, wasm-bindgen WASM bindings, and a standalone iptocc crate. The Python public API stays compatible for the common case (see Migrating from 2.x).
- Offline lookup, no external API calls.
- IPv4 and IPv6 in a single call.
- Accepts a single address or a batch of addresses.
- Lookup data embedded in the binary; no runtime file I/O after startup.
- Sub-microsecond per-call latency on native Rust; low hundreds of nanoseconds through Python or WebAssembly.
iptoccCLI installed by all three ecosystems.- Database refreshed nightly from the five Regional Internet Registries.
pip install iptocccargo add iptoccnpm install @roniemartinez/iptoccfrom iptocc import country_code
# Single lookup
country_code("8.8.8.8") # "US"
country_code("2001:4860:4860::8888") # "US"
country_code("10.0.0.0") # None
# Batch lookup (any iterable of strings)
country_code(["8.8.8.8", "1.0.16.1", "10.0.0.0"])
# ["US", "JP", None]use iptocc::{country_code, country_codes};
let cc = country_code("8.8.8.8");
assert_eq!(cc, Some("US"));
let codes = country_codes(["8.8.8.8", "1.0.16.1", "10.0.0.0"]);
assert_eq!(codes, vec![Some("US"), Some("JP"), None]);const { country_code } = require("@roniemartinez/iptocc");
country_code("8.8.8.8"); // "US"
country_code(["8.8.8.8", "1.0.16.1"]); // ["US", "JP"]$ iptocc 8.8.8.8
US
$ iptocc 8.8.8.8 1.0.16.1 10.0.0.0 193.0.6.139
8.8.8.8 US
1.0.16.1 JP
10.0.0.0 -
193.0.6.139 NL| Feature | iptocc 2.x (legacy) | ip_to_country | iptocc 3.x (this) |
|---|---|---|---|
| IPv4 | ✅ | ✅ | ✅ |
| IPv6 | ✅ | ✅ | |
| Offline lookup | ✅ | ✅ | ✅ |
| Batch API | ✅ | ||
| CLI | ✅ | ||
| Python | ✅ | ✅ | ✅ |
| Rust | ✅ | ||
| WASM | ✅ | ||
| Nightly data refresh | ✅ | ✅ |
Measured on an Apple M3 Pro across the same 1,000 unique IPv4 addresses, one-shot wall-clock timing via time.perf_counter(). Lookup-only; one-time data load is excluded. Lower is better.
| Library | 1 lookup | 1,000 lookups (total) | Relative to ip_to_country |
|---|---|---|---|
ip_to_country (bisect over array.array) |
~1.2 us | ~1.2 ms (loop; no batch API) | 1x (baseline) |
| iptocc 2.1.2 (legacy, pandas DataFrame filter) | ~78 ms | ~78 s (loop; no batch API) | ~65,000x slower |
| iptocc 3.x (Rust + PyO3) | ~100 ns | ~28 us (batch) | ~42x faster |
So iptocc 3.x is roughly ~42x faster than ip_to_country on a 1,000-address workload, while adding IPv6, a batch API, a CLI, and Rust/WASM bindings. (The legacy 2.1.2 row is included for historical context; its pandas-based filtering is orders of magnitude slower than any packed-binary approach.)
See BENCHMARK.md for the full per-RIR breakdown and the Rust-core / WASM numbers.
The legacy iptocc.get_country_code(ip) is now iptocc.country_code(ip). The return value is still str | None on a hit or miss.
# 2.x
from iptocc import get_country_code, CountryCodeNotFound
try:
cc = get_country_code("8.8.8.8")
except CountryCodeNotFound:
cc = None
# 3.x
from iptocc import country_code
cc = country_code("8.8.8.8") # returns None on miss, no exceptionLookups are based on the delegated extended statistics published by the five Regional Internet Registries:
- AFRINIC:
https://ftp.afrinic.net/stats/afrinic/delegated-afrinic-extended-latest - ARIN:
https://ftp.arin.net/pub/stats/arin/delegated-arin-extended-latest - APNIC:
https://ftp.apnic.net/public/apnic/stats/apnic/delegated-apnic-extended-latest - LACNIC:
https://ftp.lacnic.net/pub/stats/lacnic/delegated-lacnic-extended-latest - RIPE NCC:
https://ftp.ripe.net/pub/stats/ripencc/delegated-ripencc-extended-latest
A nightly GitHub Action refreshes these files, regenerates the embedded lookup tables, bumps the package versions, and publishes new releases automatically.
Common dev tasks are exposed via Taskfile:
task test # run Rust, Python, and WASM tests
task bench # run benchmarks across all three runtimes
task build:python # rebuild the pyo3 extension
task build:wasm # rebuild the wasm-pack nodejs bundleSee BENCHMARK.md for performance numbers and how to reproduce them.
Licensed under either of Apache License, Version 2.0 or MIT license at your option.
Thanks goes to these wonderful people (emoji key):
Ronie Martinez 💻 📖 🚇 🚧 👀 |
Tate Barber 💻 📖 🚇 |
mathgeek12 🐛 |
James Dolan 🤔 |
This project follows the all-contributors specification. Contributions of any kind welcome!