Skip to main content

localis

Fast, offline access to comprehensive data for countries, subdivisions, and cities. Built on ISO 3166 and GeoNames datasets with support for exact lookups, filtering, and fuzzy search.

Features

  • 🌍 254 countries with ISO codes (alpha-2, alpha-3, numeric)
  • 🗺️ 51,684 subdivisions administrative levels 1 & 2
  • 🏙️ 472,613 cities sourced from GeoNames
  • 🔍 Search Engine for typo-tolerant lookups with 99%+ accuracy
  • ⚡ Blazing fast - full dataset loads in 1.1s, lookups < 5ms, searches < 30ms
  • 📌 Aliases - support for colloquial, historic and alternate names

Installation

pip install localis

Quick Start

import localis

# Countries
country = localis.countries.lookup("US")
print(country.name)  # "United States"

# Subdivisions
state = localis.subdivisions.lookup("US-CA")
print(state.name)  # "California"

# Fuzzy search
results = localis.countries.search("Austrlia")  # Typo-tolerant
print(results[0][0].name)  # "Australia"

Countries API

Get

import localis

# By localis ID
country = localis.countries.get(1)

Returns: Country object or None

Lookup

# By alpha-2 code
country = localis.countries.lookup("GB")

# By alpha-3 code
country = localis.countries.lookup("GBR")

# By numeric code
country = localis.countries.lookup(826)

Returns: Country object or None

Filter

# Exact name match (searches name, official_name, and aliases)
results = localis.countries.filter(name="Canada")

# General query across all fields
results = localis.countries.filter(name="United", limit=5)

Returns: list[Country]

# Typo-tolerant search
results = localis.countries.search("Germny", limit=5)

for country, score in results:
    print(f"{country.name}: {score}")
# Output:
# Germany: 0.951
# Guernsey: 0.714
# ...

Returns: list[tuple[Country, float]] - sorted by similarity score

Iteration

# Iterate over all countries
for country in localis.countries:
    print(country.name)

# Get count
total = len(localis.countries)

Country Object

country = localis.countries.lookup("US")

country.id            # Database ID
country.name          # "United States"
country.official_name # "United States of America"
country.alpha2        # "US"
country.alpha3        # "USA"
country.numeric       # 840
country.aliases       # list[str] - Alternate names
country.flag          # "🇺🇸" - Unicode flag emoji

# Utility methods
country.to_dict()     # Convert to dictionary
country.json()        # Convert to JSON string

Subdivisions API

Get by ID

import localis

# By localis ID
subdivision = localis.subdivisions.get(1)

Returns: Subdivision object or None

Lookup by identifier

# By ISO code (country-subdivision)
subdivision = localis.subdivisions.lookup("US-CA")

# By GeoNames code
subdivision = localis.subdivisions.lookup("US.CA")

Returns: Subdivision object or None

Filter

# Exact name match
results = localis.subdivisions.filter(name="California")

# By subdivision type
results = localis.subdivisions.filter(type="state")

# By country
results = localis.subdivisions.filter(country="United States")

# By admin level (1 = states/provinces, 2 = counties/districts)
results = localis.subdivisions.filter(admin_level=1)

# Combine multiple filters (AND logic)
results = localis.subdivisions.filter(
    country="US",
    type="state",
    limit=10
)

Returns: list[Subdivision]

Fuzzy Search

results = localis.subdivisions.search("Californa", limit=3)

for subdivision, score in results:
    print(f"{subdivision.name}: {score}")
# California: 0.94
# Baja California: 0.8
# ...

Returns: list[tuple[Subdivision, float]]

Subdivision Object

subdivision = localis.subdivisions.lookup("US-CA")

subdivision.id              # Database ID
subdivision.name            # "California"
subdivision.geonames_code   # "US.CA"
subdivision.iso_code        # "US-CA"
subdivision.type            # "State"
subdivision.admin_level     # 1
subdivision.parent          # SubdivisionBase | None - Parent subdivision
subdivision.country         # CountryBase object
subdivision.aliases         # list[str] - Alternate names

# Utility methods
subdivision.to_dict()       # Convert to dictionary
subdivision.json()          # Convert to JSON string

Cities API

Get by ID

import localis

# By localis ID
city = localis.cities.get(1)

Returns: City object or None

Lookup by identifier

# By GeoNames ID
city = localis.cities.lookup(5128581)

Returns: City object or None

Filter

# Exact name match
results = localis.cities.filter(name="Los Angeles")

# By country name or alpha2/alpha 3 code
results = localis.cities.filter(country="United States", limit=10)

# By subdivision name or ISO/GeoNames code
results = localis.cities.filter(subdivision="California", limit=10)

# Combine filters (AND logic)
results = localis.cities.filter(
    country="US",
    subdivision="California",
    limit=20
)

Returns: list[City]

Fuzzy Search

results = localis.cities.search("Los Angelos", limit=5)

for city, score in results:
    print(f"{city.name}, {city.country.name}: {score}")

Returns: list[tuple[City, float]] - sorted by similarity score

City Object

city = localis.cities.lookup(5128581) # GeoNames ID

city.id              # Database ID
city.geonames_id     # 5128581
city.name            # "New York"
city.admin1          # SubdivisionBase | None - Primary subdivision
city.admin2          # SubdivisionBase | None - Secondary subdivision
city.country         # CountryBase object
city.population      # 8175133 | None
city.lat             # 40.71427
city.lng             # -74.00597

# Utility methods
city.to_dict()       # Convert to dictionary
city.json()          # Convert to JSON string

Base Objects

Basic versions of country and subdivision when nested.

CountryBase Object

nested_country = subdivision.country

nested_country.id
nested_country.name
nested_country.alpha2
nested_country.alpha3

SubdivisionBase Object

nested_sub = city.admin1

nested_sub.id
nested_sub.name
nested_sub.geonames_code
nested_sub.iso_code
nested_sub.type

Performance

Caching

Countries and Subdivisions are eager-loaded on import, but Cities are not due to their large dataset. All registry methods lazy load their respective indexes on first use, incurring a cold start cost. Indexes (and cities) can be pre-loaded with .force_cache() to avoid this during queries.

  • Full dataset eager load: ~1.1s (all 524k+ entities)
  • Countries (249): < 5ms for all indexes
  • Subdivisions (51,684): ~350ms for all indexes
  • Cities (472,613)
    • Lookup index: ~150ms
    • Filter index: ~1.1s
    • Search index: ~1.7s
  • Total load time: ~4.3s for all datasets and indexes

Note: These are best-case timings on modern hardware. Actual load times may vary depending on the host system.

Concurrency: It is recommended to call .force_cache() on all registries if they will be accessed from multiple threads to avoid potential race conditions during the first access of any lazy-loaded caches and indexes.

Query Performance

  • Countries:
    • All queries < 2ms
  • Subdivisions:
    • Lookups < 1ms
    • Filters < 3ms
    • Searches ~3ms
  • Cities:
    • Lookups < 5ms
    • Filters ~5ms
    • Searches < 30ms

Search Accuracy

Fuzzy search accuracy on mangled/misspelled queries:

  • Countries: 100%
  • Subdivisions: 94% (tested on 5,000 samples)
  • Cities: 99%+ (tested on 5,000 samples with city + admin1 context)

Data Sources

Data in this project is kept current monthly from the following sources:

  • Countries
  • Subdivisions
  • Cities
    • GeoNames allCountries.txt dataset (cities with population data, filtered by feature codes)
    • Feature Codes used for cities: PPL, PPLA, PPLA2, PPLA3, PPLA4, PPLA5, PPLC, PPLF, PPLL, PPLS, STLMT

Requirements

  • Python 3.11+
  • rapidfuzz - Fast fuzzy string matching
  • unidecode - Unicode text normalization

License

MIT


Contributing

Pull requests welcome at github.com/dstoffels/localis Report issues: https://github.com/dstoffels/localis/issues

Release files for localis 1.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for localis 1.1.1
File Size Uploaded
localis-1.1.1.tar.gz 26.5 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for localis 1.1.1
File Interpreter ABI Platform
localis-1.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 53.4 MB

Release files / localis-1.1.1.tar.gz

Download URL localis-1.1.1.tar.gz
Size 26.5 MB
Tags Source
SHA-256 checksum
How to use checksums
2d26bb3a306d357d572f26abb8a7be6e32caa6612799bc7b4c5783e0c5dc7ced
BLAKE2b-256 checksum
How to use checksums
3d8f27a58cdc1f099192e2c2b3a382b753d0f28f04da209171f6f04bb53f9891
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 28, 2026.

Transparency log

Release files / localis-1.1.1-py3-none-any.whl

Download URL localis-1.1.1-py3-none-any.whl
Size 26.9 MB
Tags Python 3
SHA-256 checksum
How to use checksums
0f23bdc9edd9721adde3e931d14d7659250d7e73bb29a010e82bcef3a831aed8
BLAKE2b-256 checksum
How to use checksums
cebb409b473376b6cab6457a054e7454033d0a0be7fe5b763af59344d6c92242
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 28, 2026.

Transparency log
Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page