mrkeyoor.com_
Tue 22 Sept 06:46 UTC
PyPIUtilsupdated 22 Sept 2026

pycountry review

pycountry packages Debian's iso-codes data behind Python collections for current and historic countries, subdivisions, languages, currencies, and scripts. Records support exact get calls, case-insensitive lookup, iteration, and fuzzy searches where implemented; gettext catalogs provide translated ISO names offline. Version 26.2.16 updates the source data to iso-codes 4.20.1, adds Python 3.13 and 3.14 support, matches initials such as UK in country fuzzy search, and repairs subdivision indexes after runtime add or remove operations. Our install confirmed a typed, pure-Python package with no direct dependencies.

Verdict

pycountry is a practical offline source when an application genuinely needs several ISO tables or their translations. For a country dropdown, compare Babel's display names; for two-letter validation alone, the measured 23 MB footprint is hard to defend.

We installed it

Lab card: what happened when we installed pycountryScreenshot of pycountry documentation
Install✓ · 0.5s1 package on disk · 23 MB
Importimport pycountry in 0.27s · pure Python · py.typed · requires Python >=3.10
Known vulns0(pip-audit)

Answers from our run

Does pycountry install cleanly?

Yes. In a fresh container with an empty cache, pip install pycountry finished in 0.5s, leaving 1 package and 23 MB on disk. pip-audit reported no known vulnerabilities.

What does pycountry need to run?

Python >=3.10, and nothing compiled: it is pure Python. In our run import pycountry succeeded in 0.27s, and the package ships py.typed for type checkers.

pycountry or iso3166: which should you use?

iso3166: Use it when the requirement stops at current ISO 3166-1 country records. pycountry is a practical offline source when an application genuinely needs several ISO tables or their translations.

When should you not use pycountry?

Only ISO 3166-1 country codes are needed. A smaller package such as iso3166 avoids installing the other standards and translation catalogs

API stability5/5The database objects, iteration, field-specific get, general lookup, fuzzy search, dict conversion, and gettext files have changed slowly. Version 26.2.16 raises the Python floor and adds an initials case to fuzzy search without replacing normal lookup code. Historical breaking changes are documented, including the old switch from get raising KeyError to returning a default and the later removal of missing-name fallbacks.
Docs4/5The README demonstrates every database with real record output, subdivision parents, fuzzy matching, gettext, dict conversion, and runtime custom entries. HISTORY explains version-specific data and compatibility changes, including the 26.2.16 index fix. The main gaps are behavioral: readers must infer that optional attributes can raise AttributeError and remember that get returns None while lookup raises LookupError.
Maintenance4/5GitHub reports 968 stars, 32 open issues and pull requests, an unarchived repository, and a latest push on August 24, 2026. Release 26.2.16 shipped in February with new Python support, an iso-codes update, fuzzy initials, and a subdivision mutation fix. The project intentionally defers source-data disputes to ISO and Debian, which limits its scope rather than indicating ignored data bugs.
Ecosystem4/5The supplied estimate is 9,565,149 weekly downloads. One import covers several ISO standards and gettext catalogs, which makes pycountry common in address, billing, import, and localization code. Its scope ends at ISO records. Babel is better for product-facing locale names, and analysis packages are better for continents, regions, or political group mappings.

Use it if

  • An application must normalize ISO country, language, currency, script, or subdivision codes without a network service
  • Address forms need ISO 3166-2 records and parent links grouped by country code
  • Historic ISO 3166-3 identifiers must remain resolvable in archived data
  • Translated ISO names should come from bundled gettext catalogs rather than an API
Skip it if

Setup reality

We installed pycountry 26.2.16 in a fresh Python 3.12 Bookworm container. pip completed in 0.5 seconds and left one package occupying 23 MB. There are zero direct dependencies and no compiled extensions. The package requires Python 3.10 or newer, carries py.typed, and reports LGPLv2 in the measured metadata. Importing pycountry worked in 0.27 seconds. pip-audit reported no known vulnerabilities.

Most of the footprint is data and translations; there is no supported extra that installs only countries or omits locales. That matters in small container images and function layers even though installation itself is simple. The records are local snapshots from Debian's iso-codes project. Version 26.2.16 uses iso-codes 4.20.1, so an ISO or Debian change after that snapshot needs a future pycountry release or an application-owned entry.

Miss behavior depends on the method. get(alpha_2='ZZ') returns None, while lookup('ZZ') and an unsuccessful fuzzy search raise LookupError. Optional attributes exist only when the standard supplies them. Read official_name, common_name, and some language codes with getattr(record, name, fallback) instead of assuming every record shares one schema. Fuzzy search returns a ranked list and can include subdivisions while resolving a country-style query.

Runtime add_entry and remove_entry calls mutate the process-level database. Version 26.2.16 fixes a bug where subdivision mutations broke the country_code index, but startup-only changes are still easier to reason about than per-request edits. For translations, select the correct gettext domain and call the translation object's gettext method; installing a process-global translation alias can leak one user's locale into another request.

Patterns

Resolve a country by known code lookup-by-any-code

import pycountry

country = pycountry.countries.lookup('de')
print(country.alpha_2, country.alpha_3, country.numeric, country.name)
# DE DEU 276 Germany

# lookup() scans every field case-insensitively:
pycountry.countries.lookup('DEU')
pycountry.countries.lookup('276')
pycountry.countries.lookup('germany')

Use a field-specific get when the input format is known; it avoids ambiguity and returns None on a miss.

Handle exact lookup misses handle-unknown-code

import pycountry

print(pycountry.countries.get(alpha_2='XX'))   # None

try:
    pycountry.countries.lookup('Narnia')
except LookupError as exc:
    print(exc)  # Could not find a record for 'narnia'

get returns None, while lookup raises LookupError. Keep those paths distinct in request validation.

Read an optional record field optional-fields

import pycountry

aland = pycountry.countries.get(alpha_2='AX')
print(getattr(aland, 'official_name', aland.name))

bengali = pycountry.languages.get(alpha_2='bn')
print(bengali.name, getattr(bengali, 'common_name', None))
# Bengali Bangla

ISO records do not all contain official_name or common_name, so getattr with a fallback is safer.

Build a code validation set validate-user-input

import pycountry
from functools import lru_cache

@lru_cache(maxsize=1)
def valid_alpha_2() -> frozenset[str]:
    return frozenset(c.alpha_2 for c in pycountry.countries)

def is_valid(code: str) -> bool:
    return code.upper() in valid_alpha_2()

Create the set once and reuse it. Fuzzy search is intended for discovery rather than validation.

Suggest countries from a name fuzzy-search

import pycountry

print(pycountry.countries.search_fuzzy('England'))
# [Country(alpha_2='GB', alpha_3='GBR', ... name='United Kingdom', ...)]

print([c.alpha_2 for c in pycountry.countries.search_fuzzy('Cote')])
# ['CI', 'FR', 'HN']

search_fuzzy returns a ranked list and raises LookupError when no candidate exists. Confirm the selected record with the user.

List subdivisions for a country subdivisions-for-country

import pycountry

for sub in sorted(pycountry.subdivisions.get(country_code='US'), key=lambda s: s.name):
    print(sub.code, sub.type, sub.name)

print(len(pycountry.subdivisions.get(country_code='US')))  # 57

A country_code query returns several records, whereas a unique code query returns one subdivision.

Follow a subdivision parent subdivision-hierarchy

import pycountry

fr_01 = pycountry.subdivisions.get(code='FR-01')
print(fr_01.name, fr_01.type)        # Ain Metropolitan department
print(fr_01.parent_code)             # FR-ARA
print(fr_01.parent.name)             # Auvergne-Rhone-Alpes
print(fr_01.country.name)            # France

Top-level subdivisions have no parent. Check parent_code before dereferencing parent.

Read a currency code currency-lookup

import pycountry

inr = pycountry.currencies.get(alpha_3='INR')
print(dict(inr))
# {'alpha_3': 'INR', 'name': 'Indian Rupee', 'numeric': '356'}

print(len(pycountry.currencies))  # 178

These records do not include decimal-place rules; use locale or payment-provider data for money formatting.

Translate an ISO name translate-names

import gettext
import pycountry

german = gettext.translation(
    'iso3166-1', pycountry.LOCALES_DIR, languages=['de']
)
german.install()
print(_('Germany'))  # Deutschland

Keep the gettext translation object scoped in web applications instead of installing a process-global translation alias.

Convert a record to a mapping cast-to-dict

import json
import pycountry

country = pycountry.countries.lookup('de')
print(json.dumps(dict(country)))
# {"alpha_2": "DE", "alpha_3": "DEU", "flag": "\ud83c\udde9\ud83c\uddea", ...}

Optional ISO fields produce different key sets across records, so normalize the response schema for an API.

Add an application-specific entry custom-entries

import pycountry

pycountry.countries.add_entry(
    alpha_2='XK', alpha_3='XXK', name='Kosovo', numeric='926'
)
print(pycountry.countries.get(alpha_2='XK'))
print(len(pycountry.countries))  # 250

pycountry.countries.remove_entry(alpha_2='XK')

Database mutation is process-wide. Apply custom entries once during startup and reset shared state in tests.

Look up a withdrawn country historic-countries

import pycountry

ussr = pycountry.historic_countries.get(alpha_3='SUN')
print(ussr.name, ussr.withdrawal_date)
# USSR, Union of Soviet Socialist Republics 1992-08-30
print(ussr.alpha_4)  # SUHH

Historic codes live in historic_countries and include fields such as alpha_4 and withdrawal_date.

Alternatives

PackageRegistryPick it when
iso3166PyPIUse it when the requirement stops at current ISO 3166-1 country records.
BabelPyPIUse it for CLDR display names, locale rules, and currency formatting in user interfaces.
country-converterPyPIUse it for messy-name conversion and analytical groupings such as continents or organizations.

More utils guides

lru-cache · ajv · type-fest · p-limit · find-up · js-yaml · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.