# pycountry wraps the ISO country, language, currency and script databases

> pycountry is a Python library that exposes Debian's iso-codes database through a small object API, covering ISO 3166 countries and subdivisions, ISO 639-3 languages, ISO 4217 currencies and ISO 15924 scripts. It deliberately refuses data patches, so upstream ISO and Debian decide what your application sees.

**pycountry/pycountry** — A Python library to access ISO country, subdivision, language, currency and script definitions and their translations.

- Repository: https://github.com/pycountry/pycountry
- Stars: 976 · Forks: 146
- Language: Python
- License: LGPL-2.1
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/pycountry-pycountry

## A pure wrapper around Debian iso-codes, with a no-patches rule

pycountry does not invent its own country list. The package includes a copy of the data from Debian's pkg-isocodes project and exposes it through a Python API, and the policy on data is stated bluntly: no changes to the data will be accepted into pycountry, because it is a pure wrapper around the ISO standard using the pkg-isocodes database from Debian as is.

That constraint defines what the library is. It is not a curated country table where a maintainer can fix a disputed name. It is a Python-shaped view of ISO 3166 for countries and subdivisions, ISO 639-3 for languages, ISO 4217 for currencies and ISO 15924 for scripts, plus translation files for the various strings. The documentation points anyone who needs to reflect a political change in the world to the ISO or Debian people rather than to the project.

For a user this means two things. The data is consistent with the standards as Debian tracks them, and you cannot patch a single entry locally without forking. If your application needs a custom territory, an alias for a disputed name or a legacy code you keep seeing in old data, that has to live in your own code, layered on top.

## Country objects expose alpha_2, alpha_3, numeric and official_name

Importing the package configures the databases, so pycountry.countries is already usable and iterable as soon as the import completes.

```python
>>> import pycountry
>>> len(pycountry.countries)
249
```

A country is a small record with a handful of fields. Lookup by alpha-2 code returns the object, and the attributes expose the values held in the standard.

```python
>>> germany = pycountry.countries.get(alpha_2='DE')
>>> germany.alpha_2
'DE'
>>> germany.alpha_3
'DEU'
>>> germany.numeric
'276'
>>> germany.name
'Germany'
```

Records carry more than the three code forms. The documentation shows a country with an official_name attribute alongside name, and the flag attribute on the record holds the country flag as a unicode emoji string. The attribute access goes through a __getattr__ method, so an unknown attribute raises AttributeError rather than returning None, which is why requesting a field that is not in the standard fails loudly. That behaviour is worth relying on. A typo in an attribute name becomes an exception at the point of use instead of a None that travels through your validation logic.

Lookups work with any of the code forms the standard provides, which matters when the identifier arriving from a payment provider, a customs form or a spreadsheet is a numeric code rather than an alpha-2 one.

## search_fuzzy normalises accents and prefers countries over subdivisions

Users type names, and names are the unreliable part of any country dataset. pycountry.countries.search_fuzzy exists for that case, and it does more than a substring match.

The documentation describes three behaviours. The search is fuzzy. It normalises unicode accents, so an unaccented query can match an accented entry. And it applies a bit of prioritization that prefers matches on country names before subdivision names, and lists countries with more matches ahead of ones with fewer matches.

That ranking matters more than it sounds. The query England does not match a country called England in the current standard, so the result comes back as the United Kingdom record, which is what a user means.

The query Cote shows the accent handling and the ordering at once, returning Cote d'Ivoire first, then France, then Honduras.

This is a convenience layer for discovery, not a lookup mechanism for keys. Once you have resolved a name to a record, store the alpha_2 or alpha_3 code, because a fuzzy search result list can change when the upstream data changes and because the ordering is a heuristic rather than a contract.

## Subdivisions form a typed hierarchy with parent links

Subdivisions are described in the documentation as more complex than countries because they provide a nested and typed structure. The collection is large, with thousands of entries.

```python
>>> len(pycountry.subdivisions)
5046
>>> de_st = pycountry.subdivisions.get(code='DE-ST')
>>> de_st.code
'DE-ST'
>>> de_st.name
'Sachsen-Anhalt'
>>> de_st.type
'Land'
```

Each subdivision carries a code, a name and a type, and the type vocabulary varies by country, from Parish to Metropolitan department. Two query styles are supported: direct access by unique code, and a country_code index that returns only the divisions of one country.

```python
>>> len(pycountry.subdivisions.get(country_code='DE'))
16
```

Some subdivisions name another subdivision as their parent, and the object exposes both the parent code and the resolved parent object, so you can walk up the hierarchy without a second query.

```python
>>> fr_01 = pycountry.subdivisions.get(code='FR-01')
>>> fr_01.parent_code
'FR-ARA'
```

Subdivisions also have a search_fuzzy method of their own, which normalises accents and prioritises matches on subdivision names. The documentation notes it is useful when the exact name or code is not known, which is the common case when a user picks a region from a free-text address.

## Historic countries keep withdrawn codes and their withdrawal dates

Old data outlives the codes in it. A record from an old invoice or a legacy user profile may name a country that no longer exists, and pycountry keeps those around in a separate database called historic_countries, containing former countries that were removed from the standard and are now covered by ISO 3166-3.

The records carry extra fields for that case, including a four-character code and the date the country was withdrawn.

```python
>>> ussr = pycountry.historic_countries.get(alpha_3='SUN')
>>> ussr.alpha_4
'SUHH'
>>> ussr.withdrawal_date
'1992-08-30'
```

That is more than a curiosity. If you validate user-supplied country codes against current data only, records from older systems start failing validation with no explanation. Matching against historic entries as well lets you migrate those records deliberately, and the withdrawal date gives you the information needed to decide whether a legacy code should be mapped forward or kept as history.

The same reasoning applies to the deletion list. ISO 3166-3 covers countries that were removed, and the current countries database is deliberately limited to those that exist now.

## Installing the package and regenerating the data

The visible documentation does not give a pip install line for end users, so there is no install command to copy from it. What the packaging metadata does tell you is that the distribution is named pycountry, it is built with poetry-core, and it requires Python 3.10 or newer.

```toml
[project]
name = "pycountry"
version = "26.2.17.dev0"
requires-python = ">=3.10"
```

The project itself is developed with poetry, and the Makefile resolves the poetry binary from the environment or falls back to a local virtual environment in .venv, running everything else through poetry run. The top-level targets matter for two different audiences. For contributors, the data target regenerates all ISO data from the upstream source, and the repository carries a generate.py script for that work, alongside a default.nix file and a poetry.lock.

Two details in the Makefile explain how the data pipeline is kept honest. There is a marker file for the data being up to date, and a separate marker for the poetry environment being ready, which lets a target skip regeneration when nothing upstream has changed.

For a user of the library, none of that matters. You install the released package and the data arrives with it.

## Where pycountry is the wrong tool: custom lists and live political changes

The no-patches policy is the library's defining limitation, and it cuts both ways. If your application needs a territory that ISO does not recognise, or a display name that differs from the standard, you cannot ask pycountry to carry it. That means keeping your own mapping layer, which reintroduces the maintenance burden the library was supposed to remove. The same applies to corrections: if an upstream dataset carries a name you dislike, your options are a wrapper or a fork.

There is also a freshness boundary. The data is a copy of Debian's iso-codes, so how quickly a change in the ISO standards reaches your application depends on how quickly Debian updates pkg-isocodes and how quickly a new pycountry release ships that copy. For most use cases that lag is irrelevant. For a sanctions list, a tax regime or a territory dispute, it is a fact to plan around rather than a bug.

The alternative approach is to hardcode a dictionary in your own project, which is trivial for a fixed list of a dozen countries and gives you total control over names and aliases. It stops being trivial the moment you need subdivisions, translations, currency codes or the numeric codes that arrive from a bank file, because then you are maintaining a subset of ISO by hand. pycountry is the better deal once you need the wider standards and can accept the upstream data as the source of truth.

## LGPL-2.1-only licensing and a calendar-shaped version number

The project is licensed LGPL-2.1, and the packaging metadata records it as LGPL-2.1-only. For a library imported into a larger application, an LGPL licence of this kind is the kind of decision that should be checked by whoever owns licensing policy in your organisation, because the obligations depend on how you distribute the work. Read the LICENSE.txt file and COPYRIGHT.txt in the repository before shipping. This is a pointer, not legal advice.

The version numbering looks calendar based: the development version in the metadata is 26.2.17.dev0, and the most recent release was 26.2.16 on 2026-02-17. The last push to the repository was on 2026-10-19, so the work is recent even though the release tag is from February. Upgrades are cheap in the ordinary sense, since the library has no runtime dependencies to reconcile and only requires Python 3.10 or newer.

The packaging metadata classifies the project as Production/Stable and lists support for Python 3.10 through 3.14. The changelog lives in HISTORY.txt, issues are reported in the GitHub repository, and the repository carries a pre-commit configuration, a tox configuration and a coverage configuration, so the test and lint setup is present even though its configuration files are not part of the visible documentation.

## Conclusion

pycountry is the right pick for applications that need to map user-entered country, currency or script names onto ISO codes and want the reference data to come from Debian rather than from a hand-maintained dictionary in the repository. It is the wrong pick if you need a country list you can edit in place, if your data must reflect a political change before Debian ships it, or if a translation-heavy user interface is the actual problem, since it ships reference data rather than UI strings. Before depending on it, check the len() of pycountry.countries and pycountry.subdivisions against the counts you expect, and confirm that the codes your upstream provider sends match the alpha_2 and alpha_3 values pycountry returns.

## FAQ

### How many countries does pycountry contain?

The documentation shows len(pycountry.countries) returning 249, and len(pycountry.subdivisions) returning 5046. Those numbers come from the ISO data as packaged, so they move when the upstream data is refreshed.

### How do I look up a country by name with pycountry?

Use pycountry.countries.search_fuzzy, which is a fuzzy search that normalises unicode accents and prioritises country name matches over subdivision matches. For exact code lookups use pycountry.countries.get with alpha_2, alpha_3 or numeric.

### Can I add or change country data in pycountry?

No. The project states that no changes to the data will be accepted because it is a pure wrapper around the ISO standard using Debian's pkg-isocodes database as is. Custom entries have to live in your own code.

### How do I get the parent region of a subdivision in pycountry?

Subdivision records expose parent_code for the code of the parent subdivision, and parent for the resolved parent object, so you can walk up the hierarchy without a second query.

### What Python version does pycountry require?

The packaging metadata sets requires-python to >=3.10 and the classifiers list Python 3.10 through 3.14. The distribution is built with poetry-core.

## Sources

- [Issues](https://github.com/pycountry/pycountry/issues)
- [License: LGPL-2.1](https://github.com/pycountry/pycountry/blob/main/LICENSE)
- [pycountry/pycountry on GitHub](https://github.com/pycountry/pycountry)
- [README](https://github.com/pycountry/pycountry/blob/main/README.md)
- [Releases](https://github.com/pycountry/pycountry/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/pycountry-pycountry
