DEV Community

Aman Sachan
Aman Sachan

Posted on • Originally published at github.com

BabelBeacon — IP geolocation in pure stdlib Python (HTTP or offline MaxMind MMDB)

The problem

Almost every backend hits the same geolocation question: where is this IP from, and what timezone is it in? Maybe you're logging a request, gating content by region, or trying to render a timestamp in the user's local time. The good news: there are great public APIs for this. The awkward news: most Python libraries for IP geolocation either pull in a heavy dependency, wrap a paid service, or both.

I wanted something I could pip install once and then forget about — small, stdlib-only by default, working offline if I needed it, and with a result object I could pass around without thinking.

So I built BabelBeacon.

What it is

  • A single GeoResult dataclass: country, region, city, lat/lon, IANA timezone, ASN/ISP
  • Two backends:
    • http — uses ip-api.com (no key, ~45 req/min, free for non-commercial)
    • mmdb — uses a local GeoLite2-City.mmdb file (offline, no rate limits)
  • Built-in lat/lon → IANA timezone resolution (no external service, no API key)
  • Haversine distance helpers in km and mi
  • In-memory cache (so you don't re-look up the same IP twice in a request)
  • A CLI: python3 -m babelbeacon 8.8.8.8

No required third-party deps. The optional MMDB path uses only Python's struct module to read the database.

Quickstart

from babelbeacon import lookup, distance_km, timezone_for

r = lookup("8.8.8.8")
print(r.country, r.city, r.timezone)
# United States, Mountain View, America/Los_Angeles

# Pure offline — no network call
tz = timezone_for(28.6, 77.2)
print(tz)  # Asia/Kolkata

# Distance between two points
d = distance_km(28.6, 77.2, 19.0, 72.8)
print(f"{d:.0f} km")  # ~1157 km
Enter fullscreen mode Exit fullscreen mode

The CLI:

$ python3 -m babelbeacon 8.8.8.8 1.1.1.1
8.8.8.8:    US  Mountain View    America/Los_Angeles  AS15169 Google
1.1.1.1:    US  San Francisco    America/Los_Angeles  AS13335 Cloudflare

$ python3 -m babelbeacon --timezone-for 28.6,77.2
Asia/Kolkata

$ python3 -m babelbeacon --distance 28.6,77.2,19.0,72.8
1157.21 km
Enter fullscreen mode Exit fullscreen mode

The two backends

HTTP (default)

lookup("8.8.8.8", backend="http")
Enter fullscreen mode Exit fullscreen mode

Hits http://ip-api.com/json/<ip>?fields=... via urllib.request. No API key. Returns the full record (country, region, city, lat, lon, timezone, ISP, ASN, ZIP).

I picked ip-api.com because:

  • Free for non-commercial use
  • No signup, no key to manage
  • Already returns a normalized JSON with timezone included (most APIs don't)

If you need production throughput, switch to MMDB.

MMDB (offline)

lookup("8.8.8.8", backend="mmdb", mmdb_path="./GeoLite2-City.mmdb")
Enter fullscreen mode Exit fullscreen mode

Reads a local MaxMind GeoLite2 database. No network call. Works in air-gapped environments. The reader is ~200 lines of pure Python + struct — no maxminddb package needed.

This is the path you'd use for:

  • Serverless / Lambda where latency matters
  • Air-gapped networks
  • Production with high QPS where a public API would rate-limit you

Timezone resolution (the part I'm most proud of)

Almost every IP geolocation API will return a country and city. Most don't return an IANA timezone — because the mapping from lat/lon → timezone is annoying and nobody wants to maintain a 200 MB polygon dataset.

The trick: there are only ~400 IANA timezones, and most of them cluster around recognizable country/region/city anchors. So BabelBeacon ships with a small built-in table of ~150 well-known anchors (Delhi, London, NYC, Tokyo, Sydney, etc.) plus a fallback: pick the closest anchor in the same country (or failing that, the closest continent-level zone) and return its timezone.

This isn't perfect — try it in the middle of the Pacific ocean and you'll get an arbitrary anchor. But for practical use cases (rendering timestamps for logged-in users, scheduling emails, picking a default locale), it works 95% of the time. And the cases where it doesn't work are exactly the cases where any approach short of a polygon database would also fail.

Honest limits

  • The HTTP backend is free but rate-limited (~45 req/min from a single IP). For production at scale, use MMDB.
  • The MMDB reader doesn't support all of MaxMind's optional data types yet (just the core city/country/location fields). PRs welcome.
  • The timezone resolver uses anchor-based nearest-neighbor, not polygons. Good enough for most use cases, not for the middle of the ocean.
  • IP-API.com's free tier is not for commercial use. If you're a business, get a paid plan or use MMDB.

Why I built it this way

I was writing a logging pipeline and didn't want to add geoip2 + maxminddb + pytz + python-dateutil for the one place I needed to print a user's local time. I just wanted one dataclass and one function. So I wrote the smallest thing that could do the job, and now it's a library.

The whole thing is ~400 lines of code, 21 unit tests, and a CLI. Easy to read, easy to vendor, easy to fork.

Try it

git clone https://github.com/AmSach/babelbeacon
cd babelbeacon
python3 -m unittest tests.test_babelbeacon
python3 -m babelbeacon 8.8.8.8
Enter fullscreen mode Exit fullscreen mode

Repo: https://github.com/AmSach/babelbeacon

MIT licensed. Issues and PRs welcome — especially the MMDB polygon-type support and more timezone anchors.

Top comments (0)