Library / SDK
maxmind/GeoIP2-python avatar
maxmind/GeoIP2-python

geoip2: one Python API over MaxMind web services and local .mmdb files

Python code for GeoIP2 webservice client and database reader

1,223 stars143 forksPythonApache-2.0

At a glance

What is it?
The geoip2 package gives Python callers the same record model whether the answer comes from the MaxMind web service or from a local MaxMind database, and it fails with typed exceptions instead of empty results. The cost is a hard Python floor at 3.10 and a data source that is only as precise as the geolocation database behind it.
Who is it for?
geoip2 is a good fit for a Python service that needs country, city or Insights records and wants the same code path whether it queries the web service or a local database, and it is a poor fit for a Python 3.9 runtime, for offline use with no MaxMind database file, or for identifying a specific household, which the README explicitly warns against.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 2 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Two data sources behind one set of record classes

GeoIP answers a narrow question: what does MaxMind's data say about this IP address. The geoip2 package exists because there are two ways to ask it, and applications that support both should not need two sets of parsing code. The first is the web service, reached over HTTP through geoip2.webservice.Client or geoip2.webservice.AsyncClient. The second is a local database file, read through geoip2.database.Reader.

The important design decision is that both paths return the same shape of answer. A lookup on either side yields a model class for the endpoint, and that model contains record classes for the parts of the data: country, subdivisions, city, postal, location and traits. The README states this for the web service, and repeats the same arrangement for the database reader. An application can therefore switch a lookup from a local file to a hosted call without rewriting the code that consumes the fields.

The package metadata shows where the weight sits. The dependencies are aiohttp, maxminddb and requests, and the project requires Python 3.10 or newer, with classifiers for 3.10 through 3.14. The build backend is uv_build, the lock file is uv.lock, and the repository ships a HISTORY.rst changelog, docs, an examples directory containing benchmark.py, and a tests directory. The homepage of the documentation is geoip2.readthedocs.org, and the project links to the MaxMind developer documentation for the web service and for the databases.

The record model and the fields that carry the answer

What comes back is not a flat dictionary but a set of small objects, and the README's example walks through the useful ones. The country record exposes iso_code, name and a names mapping that holds localized names, so a caller can ask for the country in the language of the end user. The subdivisions record has a most_specific member, which is the level that actually identifies the state, province or region, along with its own iso_code. The city record has a name, the postal record has a code, and the location record carries latitude and longitude. The traits record carries the network, returned as a network object for the address that was looked up.

The most_specific accessor is a small design choice with real consequences. Subdivision lists vary in depth between countries, and asking for the last element of the list sidesteps the question of which level applies. A caller who wants a two-level breakdown still has to walk the list themselves, and the README does not describe any helper for that.

The accuracy of these fields is the other half of the story, and the README is unusually direct about it. IP geolocation is described as inherently imprecise, locations are often near the center of the population, and any location from a GeoIP database or web service should not be used to identify a particular address or household. That warning is part of the documentation rather than a footnote on a policy page, and it constrains what a sensible application should do with a coordinate it gets back.

Installing the package and running a first lookup

Installation is a single package from PyPI:

bash
pip install geoip2

If the environment cannot reach PyPI, the README points at installing from a source checkout with pip:

bash
python -m pip install .

A first web service lookup constructs a client with your MaxMind account id and licence key, then calls the method that matches the endpoint you want. The README notes that the client object can be reused across requests, which is the point of keeping it around rather than building one per call:

python
import geoip2.webservice

with geoip2.webservice.Client(42, 'license_key') as client:
    response = client.city('203.0.113.0')

    response.country.iso_code
    response.country.name
    response.subdivisions.most_specific.name
    response.city.name
    response.postal.code
    response.location.latitude
    response.location.longitude

The literal values 42 and 'license_key' in the README are placeholders for your own credentials, and the address in the example is from the documentation range rather than a real host. The country, city and insights endpoints are all available on the same client, and the README points out that Insights is not supported by the GeoLite web service, so the method you can call depends on which service the host argument selects.

The local database route is shorter. You construct geoip2.database.Reader with the path to the file as the first argument, then call the method that matches the database type, such as city or country, passing it the address you want to look up. The reader holds the file open, so it is meant to be created once and reused rather than rebuilt for every address.

Host selection and the error taxonomy of the web service client

Three hosts are reachable through the same constructor, chosen with the optional host keyword argument. The default is the production GeoIP web service. Setting host to geolite.info routes requests to the GeoLite web service instead. Setting host to sandbox.maxmind.com points the client at the Sandbox GeoIP web service, which is where you would point a test that must not consume paid queries against the production endpoint. A single constructor signature therefore carries both the product tier and the environment.

Errors are the other half of the contract, and they are specific rather than generic. When the web service returns an explicit error document, the client raises AddressNotFoundError, AuthenticationError, InvalidRequestError or OutOfQueriesError as appropriate, and all four subclass GeoIP2Error. Those four cover the cases an application usually needs to treat differently: an address that simply is not in the database, credentials that are wrong, a malformed request, and an exhausted query allowance.

Anything else that goes wrong at the HTTP level becomes an HTTPError. That covers a 500 from the service, an invalid error document, and any status code that is not 200, 4xx or 5xx. There is one more case worth noting: if the service returns a 200 but the body is invalid, the client raises GeoIP2Error rather than HTTPError, because the transport succeeded and the payload did not.

The consequence for application code is that catching GeoIP2Error alone is not enough to make a retry decision, and that is deliberate. A 500 is worth retrying, an AuthenticationError is not, and OutOfQueriesError means the account, not the request, has a problem. The database reader also throws an exception when a lookup fails, though the README does not enumerate the database error types in the same way it does for the web service.

Local databases, the maxminddb dependency and the optional C extension

The database side of the package is where the offline story lives. A reader is pointed at a .mmdb file and answers lookups without a network call, which suits a service that must keep working when the hosted API is unreachable, and suits deployments that would rather not put a licence key in a request path.

The file reading itself is handled by the maxminddb dependency, declared as maxminddb>=3.0.0,<4.0.0. On top of that, the README describes a database reader extension built as a C extension, and it is explicit that using it requires installing the libmaxminddb C API first, with the instructions shipped with that library. The C extension is therefore an opt-in step, not something the pip install handles for you, and a deployment that wants it has to build a native dependency into its image. The pure Python path remains available for environments that would rather not.

The examples directory contains benchmark.py, which indicates that lookup latency is something the project measures rather than leaves unexamined. What it does not do is put a number in the README, so anyone with a latency budget of their own has to run that script against their own data.

The databases themselves are not part of this package. The README links to the MaxMind database documentation, and the tests directory references test data under tests/data/test-data, which is fixture data for the test suite rather than a distributable database. The data, its format and its update cadence belong to MaxMind's own products, and choosing between the paid GeoIP databases and the free GeoLite ones is a decision this library does not make for you.

The async client and the event loop it is bound to

An asynchronous client is provided, and the README's async example is close to a copy of the synchronous one with awaits in the right places. The construction is the same, with the credentials passed to geoip2.webservice.AsyncClient, and the example is used as an async context manager:

python
import asyncio
import geoip2.webservice

async def main():
    async with geoip2.webservice.AsyncClient(42, 'license_key') as client:
        response = await client.city('203.0.113.0')
        response.country.iso_code

asyncio.run(main())

The subtlety is written into a comment in the README rather than into a separate section: the client can be reused across requests on the running event loop, but if you use multiple event loops you must ensure the object is not used on another loop. That is a real constraint for applications built on a framework that starts a loop per request, or for code that mixes a synchronous entry point with an asynchronous worker in the same process. The safe pattern is to construct the client inside the loop that will use it, and the README's example does exactly that by creating it inside main.

The aiohttp dependency, pinned to aiohttp>=3.14.1,<4.0.0, is what the asynchronous path rests on, and requests, pinned to requests>=2.24.0,<3.0.0, serves the synchronous path. Carrying both means a deployment that uses only one of the two clients still installs both libraries, which is a reasonable trade for keeping one API surface.

Where geoip2 is the wrong instrument

The clearest case is the one the README already names. If a requirement needs to identify a particular address or household, this data cannot support it, and no amount of configuration changes that. Geolocation answers to a city, a region or a country, often with a coordinate near the middle of a populated area. Applications that present a country label, flag a request by jurisdiction, or bias content towards a language are using it correctly. Applications that attempt per-household targeting are not.

The second case is data availability. A local .mmdb file has to be obtained and refreshed, and the README does not describe an update mechanism or a subscription for the database files. A service that wants geolocation on every request without a MaxMind account has no path through this package, since the web service side requires an account_id and a license_key.

The third case is the runtime floor. Python 3.10 is the minimum, and the classifiers stop at 3.14, so an environment pinned to an older interpreter is out. That is cheap to check and expensive to discover late, so it belongs in the first hour of evaluation rather than the last.

A fourth boundary is precision of the source itself. Even a correct call returns what the database believes, and the database is a snapshot that ages. A country code that changes, a network that gets reassigned, or a city boundary that moves will all produce answers that were right at publication time. The library does not report a confidence figure, so applications that need one have to source it elsewhere.

Licence, version history and what an upgrade costs

The licence is Apache-2.0, stated in the package metadata and shipped in a LICENSE file at the repository root. That licence permits use in proprietary products, requires preservation of the copyright and licence notices on redistribution, and carries an explicit patent grant, which is worth noting when a library is embedded in a product rather than an internal tool. The description here covers the published terms, not legal advice.

The version history is published in HISTORY.rst, and the release cadence is visible in the tags. Version 5.1.0 came out on 2025-05-05, 5.2.0 on 2025-11-20 and 5.3.0 on 2026-07-16, and the repository is not archived, with the last push on 2026-10-19. The current metadata version is 5.3.0, and the dependencies are expressed as ranges rather than exact pins, with upper bounds below the next major release for aiohttp, maxminddb and requests.

Those ranges are the main upgrade consideration. A minor release can move the resolved version of three libraries inside their permitted ranges, so a routine pip upgrade of geoip2 can change the version of the HTTP stack underneath it. The classifiers and the changelog are the places to look for what changed, and the project's own lock file, uv.lock, shows the set of versions its maintainers build and test against.

There is also a packaging shift to keep in mind. The build backend is uv_build rather than setuptools, which matters for organisations whose internal build pipeline assumes a setuptools-based project.

Editorial conclusion

geoip2 is a good fit for a Python service that needs country, city or Insights records and wants the same code path whether it queries the web service or a local database, and it is a poor fit for a Python 3.9 runtime, for offline use with no MaxMind database file, or for identifying a specific household, which the README explicitly warns against. Before adopting it, confirm which MaxMind product and licence your account covers, since the GeoLite web service does not support Insights, and check that the pinned aiohttp, maxminddb and requests ranges in pyproject.toml fit the rest of your dependency set.

Frequently asked questions

How do I install the geoip2 package?

The README gives pip install geoip2 for the standard case. If PyPI is not reachable, the same section points at python -m pip install . from a source checkout.

What is the difference between the geoip2 web service client and the database reader?

The web service client, geoip2.webservice.Client or AsyncClient, looks an address up over HTTP using your account_id and license_key, while geoip2.database.Reader looks it up in a local .mmdb file. Both return the same model and record classes.

Which exceptions does the geoip2 web service client raise?

An explicit error document from the service becomes AddressNotFoundError, AuthenticationError, InvalidRequestError or OutOfQueriesError, all of which subclass GeoIP2Error. Other HTTP failures become HTTPError, and a 200 response with an invalid body raises GeoIP2Error.

Do I need the libmaxminddb C library to use geoip2?

Only if you want the C extension for the database reader. The README states that using it requires installing the libmaxminddb C API first, following the instructions distributed with that library.

Can I use the GeoLite web service with the geoip2 client?

Yes. Set the optional host keyword argument to geolite.info to use the GeoLite web service instead of the GeoIP one, and to sandbox.maxmind.com for the Sandbox service. The README notes that Insights is not supported by the GeoLite web service.

Official sources

  1. License: Apache-2.0
  2. maxmind/GeoIP2-python on GitHub
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/maxmind-geoip2-python.svg)](https://hysenlabs.com/projects/maxmind-geoip2-python)