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]
Fuzzy Search
# 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
- ISO 3166-1 data via Debian's iso-codes project
- Geonames
geonames_countries.txt - Additional country aliases from Wikidata.
- Subdivisions
- ISO 3166-2 data via Ipregistry
- GeoNames
admin1CodesASCII.txtandadmin2Codes.txt
- Cities
- GeoNames
allCountries.txtdataset (cities with population data, filtered by feature codes) - Feature Codes used for cities: PPL, PPLA, PPLA2, PPLA3, PPLA4, PPLA5, PPLC, PPLF, PPLL, PPLS, STLMT
- GeoNames
Requirements
- Python 3.11+
rapidfuzz- Fast fuzzy string matchingunidecode- 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)
| File | Size | Uploaded | |
|---|---|---|---|
| localis-1.1.1.tar.gz | 26.5 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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 logRelease 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