Fast, offline access to comprehensive geographic data for countries, subdivisions, and cities. Built on ISO 3166 and GeoNames datasets with support for exact lookups, filtering, and fuzzy search.
Project description
localis
Fast, offline access to comprehensive geographic data for countries, subdivisions, and cities. Built on ISO 3166 and GeoNames datasets with support for exact lookups, filtering, and fuzzy search.
Features
- 🌍 249 countries with ISO codes (alpha-2, alpha-3, numeric)
- 🗺️ 5,000+ subdivisions (states, provinces, regions, etc.)
- 🏙️ 451,000+ cities (optional GeoNames dataset, requires manual installation)
- 🔍 Search Engine for typo-tolerant lookups with 90%+ accuracy
- ⚡ Blazing fast - all data stored offline in optimized SQLite with FTS5
- 🔌 Aliases - support for colloquial, historic and alternate names.
Installation
pip install localis
Quick Start
import localis
# Countries
country = localis.countries.get(alpha2="US")
print(country.name) # "United States"
# Subdivisions
state = localis.subdivisions.get(iso_code="US-CA")
print(state.name) # "California"
# Fuzzy search
results = localis.countries.search("Austrlia") # Typo-tolerant
print(results[0][0].name) # "Australia"
Countries API
Get by identifier
import localis
# By alpha-2 code
country = localis.countries.get(alpha2="GB")
# By alpha-3 code
country = localis.countries.get(alpha3="GBR")
# By numeric code
country = localis.countries.get(numeric=826)
# By database ID
country = localis.countries.get(id=1)
Returns: Country object or None
Filter
# Exact name match
results = localis.countries.filter(name="Canada")
# By official name
results = localis.countries.filter(official_name="United Kingdom")
# By alternative name
results = localis.countries.filter(alt_name="Holland")
# General query across all fields
results = localis.countries.filter(query="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)
# Access by index
country = localis.countries[0]
# Get count
total = len(localis.countries)
Country Object
country = localis.countries.get(alpha2="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.alt_names # list[str] - Alternative names
country.flag # "🇺🇸" - Unicode flag emoji
# Utility methods
country.to_dict() # Convert to dictionary
country.json() # Convert to JSON string
Subdivisions API
Get by identifier
import localis
# By ISO code (country-subdivision)
subdivision = localis.subdivisions.get(iso_code="US-CA")
# By GeoNames code
subdivision = localis.subdivisions.get(geonames_code="US.CA")
# By database ID
subdivision = localis.subdivisions.get(id=1)
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 alternative name
results = localis.subdivisions.filter(alt_name="Cali")
# General query across all fields
results = localis.subdivisions.filter(query="New", 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.92
# Baja California 0.763
Returns: list[tuple[Subdivision, float]]
Get by Country
# Get all subdivisions for a country
us_subdivisions = localis.subdivisions.for_country(alpha2="US")
# Filter by admin level (1 = states/provinces, 2 = counties/districts)
states = localis.subdivisions.for_country(alpha2="US", admin_level=1)
counties = localis.subdivisions.for_country(alpha2="US", admin_level=2)
# Can also use alpha3, numeric, or id
subdivisions = localis.subdivisions.for_country(alpha3="CAN")
subdivisions = localis.subdivisions.for_country(numeric=124)
Returns: list[Subdivision]
Get Subdivision Types
# Get all subdivision types for a country
types = localis.subdivisions.types_for_country(alpha2="GB")
print(types) # ["Country", "Province", "District", ...]
# Filter by admin level
types = localis.subdivisions.types_for_country(alpha2="US", admin_level=1)
print(types) # ["State", "District", "Outlying area"]
Returns: list[str]
Subdivision Object
subdivision = localis.subdivisions.get(iso_code="US-CA")
subdivision.id # Database ID
subdivision.name # "California"
subdivision.iso_code # "US-CA"
subdivision.geonames_code # "US.CA"
subdivision.type # "State"
subdivision.country # "United States"
subdivision.country_alpha2 # "US"
subdivision.country_alpha3 # "USA"
subdivision.admin_level # 1
subdivision.parent_id # int | None - Parent subdivision ID
subdivision.alt_names # list[str] - Alternative names
# Utility methods
subdivision.to_dict() # Convert to dictionary
subdivision.json() # Convert to JSON string
Cities API
⚠️ Important: Loading Cities Data
The cities dataset is NOT included by default due to its size (250MB+, 451,000 records).
Load cities data:
# Via CLI
localis load cities
# Or in Python
import localis
localis.cities.load()
This will:
- Copy the database to your project root as
localis.db - Download the cities.tsv fixture
- Load 451,000+ cities into the database
- Add
localis.dbto your.gitignore - Create a
.localis.conffile to track the database location
Unload cities data:
# Via CLI
localis unload cities
# Or in Python
localis.cities.unload()
This removes the external database, config file, and reverts to the bundled read-only database.
Get by identifier
import localis
# By database ID
city = localis.cities.get(id=1)
# By GeoNames ID
city = localis.cities.get(geonames_id="5128581")
Returns: City object or None
Filter
# Exact name match
results = localis.cities.filter(name="Los Angeles")
# By country
results = localis.cities.filter(country="United States", limit=10)
# By subdivision (admin1)
results = localis.cities.filter(admin1="California", limit=10)
# By admin2 (county/district)
results = localis.cities.filter(admin2="Los Angeles County", limit=5)
# By alternative name
results = localis.cities.filter(alt_name="LA")
# General query across all fields
results = localis.cities.filter(query="San Diego", 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}: {score}")
Returns: list[tuple[City, float]] - sorted by population and similarity
Get Cities by Country
# Get all cities in a country
cities = localis.cities.for_country(alpha2="US")
# Filter by population
large_cities = localis.cities.for_country(
alpha2="US",
population__gt=1000000
)
small_cities = localis.cities.for_country(
alpha2="US",
population__lt=50000
)
# Can also use alpha3, numeric, or id
cities = localis.cities.for_country(alpha3="FRA")
cities = localis.cities.for_country(numeric=250)
Returns: list[City]
Get Cities by Subdivision
# Get all cities in a subdivision
cities = localis.cities.for_subdivision(iso_code="US-CA")
# Filter by population
cities = localis.cities.for_subdivision(
geonames_code="US.CA",
population__gt=500000
)
# Can also use id
cities = localis.cities.for_subdivision(id=123)
Returns: list[City]
City Object
city = localis.cities.get(geonames_id="5128581")
city.id # Database ID
city.geonames_id # 5128581
city.name # "New York"
city.display_name # str | None - Display name if different from name
city.subdivisions # list[SubdivisionBasic] - Parent subdivisions
city.country # "United States"
city.country_alpha2 # "US"
city.country_alpha3 # "USA"
city.population # 8175133 | None
city.lat # 40.71427
city.lng # -74.00597
city.alt_names # list[str] - Alternative names
# Utility methods
city.to_dict() # Convert to dictionary
city.json() # Convert to JSON string
# SubdivisionBasic structure
city.subdivisions[0].name # "New York"
city.subdivisions[0].geonames_code # "US.NY"
city.subdivisions[0].iso_code # "US-NY"
city.subdivisions[0].admin_level # 1
CLI Commands
# Check dataset status
localis status
# Load cities dataset
localis load cities
# Auto-confirm load
localis load cities -y
# Load to custom directory
localis load cities -p ./data
# Unload cities dataset
localis unload cities
Data Sources
- Countries & Subdivisions: ISO 3166 data via Ipregistry
- Cities: GeoNames
allCountries.txtdataset (cities with population info, filtered by feature code)
Performance Notes
- Countries: All queries < 1ms.
- Subdivisions: Cache time < 1s, search queries 8-13ms @ 95% accuract
- Cities: Fast SQLite queries with FTS5 full-text search (avg 48ms @ 89% accuracy)
- Search Enginer: Diminishing token prefix truncation for FTS5 candidacy fed into rapidfuzz
- Database size:
- Base (countries/subdivisions): 16MB
- With cities: 251MB
Requirements
- Python 3.9+
rapidfuzzrequired for search featuresrequestsrequired for loading cities datasettyperrequired for CLI
License
MIT
Contributing
Issues and pull requests welcome at github.com/dstoffels/localis
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file localis-1.0.0a1.tar.gz.
File metadata
- Download URL: localis-1.0.0a1.tar.gz
- Upload date:
- Size: 4.7 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
578cfef5cbb20e9733aef4f11b7aab08ae686ea2ff4e57c34ff9cb6c5d134d2d
|
|
| MD5 |
4fbac183c3d6c3042dfefbef3269ab02
|
|
| BLAKE2b-256 |
e6b7b4f56d57be6177b646ebef22c02132eb81ea06518595631e71ef4d970200
|
Provenance
The following attestation bundles were made for localis-1.0.0a1.tar.gz:
Publisher:
release.yaml on dstoffels/localis
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
localis-1.0.0a1.tar.gz -
Subject digest:
578cfef5cbb20e9733aef4f11b7aab08ae686ea2ff4e57c34ff9cb6c5d134d2d - Sigstore transparency entry: 711524860
- Sigstore integration time:
-
Permalink:
dstoffels/localis@e52bf30bc0f2b5110aa4bf9f9e639c42ba695058 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/dstoffels
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yaml@e52bf30bc0f2b5110aa4bf9f9e639c42ba695058 -
Trigger Event:
push
-
Statement type:
File details
Details for the file localis-1.0.0a1-py3-none-any.whl.
File metadata
- Download URL: localis-1.0.0a1-py3-none-any.whl
- Upload date:
- Size: 4.8 MB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
acbf6591e6dfc779a7ebc8deb7b1d13099e4a1d953d0e128cc49f967c4fed993
|
|
| MD5 |
d3f9ed3f60b4122a8fcaa6a4e0e12e8b
|
|
| BLAKE2b-256 |
f45d4222a40390d2b8f215ac448bf87084fd1ef58ccbb347f7ba34bd5de93ded
|
Provenance
The following attestation bundles were made for localis-1.0.0a1-py3-none-any.whl:
Publisher:
release.yaml on dstoffels/localis
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
localis-1.0.0a1-py3-none-any.whl -
Subject digest:
acbf6591e6dfc779a7ebc8deb7b1d13099e4a1d953d0e128cc49f967c4fed993 - Sigstore transparency entry: 711524864
- Sigstore integration time:
-
Permalink:
dstoffels/localis@e52bf30bc0f2b5110aa4bf9f9e639c42ba695058 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/dstoffels
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yaml@e52bf30bc0f2b5110aa4bf9f9e639c42ba695058 -
Trigger Event:
push
-
Statement type: