Skip to main content

localis

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

Features

  • 🌍 254 countries sourced and merged from ISO 3166-1 and GeoNames
  • 🗺️ 51,684 subdivisions sourced and merged from ISO 3166-2 and GeoNames
  • 🏙️ 235,895 cities sourced from GeoNames cities500.txt
  • 🔍 Search Engine for typo-tolerant lookups with 99%+ accuracy
  • 📌 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 GeoNames id

# 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
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 registries are eager-loaded on import, Cities are lazy-loaded due to its 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 the registry's .force_cache() to avoid this during queries.

Countries (254)

Component Load Time Memory
Dataset < 1ms 0KB
Lookup index < 1ms 102.4KB
Filter index < 1ms 307.2KB
Search index ~1ms 512.0KB
Combined ~2ms 921.6KB

Subdivisions (51,684)

Component Load Time Memory
Dataset < 1ms 0KB
Lookup index ~13ms 3.7MB
Filter index ~95ms 11.3MB
Search index ~39ms 23.3MB
Combined ~147ms 38.3MB

Cities (235,895)

⚠️ Memory-intensive. Fully caching cities and its indexes adds 212.5MB of resident memory. Calling localis.cities.force_cache() loads all of it upfront.

Component Load Time Memory
Dataset ~352ms 57.0MB
Lookup index ~65ms 921.6KB
Filter index ~566ms 57.8MB
Search index ~155ms 96.8MB
Combined ~1.14s 212.5MB

Full Cache: ~1.3s load time, 278.9MB memory for all datasets and indexes

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.

Benchmarks

Per-call query latency, and fuzzy search accuracy on mangled/misspelled queries:

Registry Get Lookup Filter Search Search Accuracy
Countries 0.0009ms 0.0029ms 0.0054ms 1.27ms 100%
Subdivisions 0.002ms 0.0059ms 0.013ms 3.28ms 94%
Cities 0.0043ms 0.0046ms 0.0166ms 4.94ms 99%+

Accuracy tested on 5,000 mangled-query samples per registry; cities' search additionally includes city + admin1 context.


Data Sources

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


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.2

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.2
File Size Uploaded
localis-1.1.2.tar.gz 21.0 MB Details

Built distribution (wheel)

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

Total release size: 42.3 MB

Release files / localis-1.1.2.tar.gz

Download URL localis-1.1.2.tar.gz
Size 21.0 MB
Tags Source
SHA-256 checksum
How to use checksums
338e6acedc746df06fb170b354fa8687bc84de7a0d8be02a5d4ca6a1584925aa
BLAKE2b-256 checksum
How to use checksums
70dd3adad9458fbf0f5ef0f0d3232da27982dfdef5ad2f349aeaa7555a6e4db5
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 29, 2026.

Transparency log

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

Download URL localis-1.1.2-py3-none-any.whl
Size 21.2 MB
Tags Python 3
SHA-256 checksum
How to use checksums
0094bdd4894900012ce0643969d95adc1497ca80394bc0c3a6f1a314c8eecdb1
BLAKE2b-256 checksum
How to use checksums
d9d3520b2490897d75eb502a82fdce5aab61166757b53b2343b06ec80a44ecb8
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 29, 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