pycountry wraps the ISO country, language, currency and script databases
A Python library to access ISO country, subdivision, language, currency and script definitions and their translations.
At a glance
- What is it?
- 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.
- Who is it for?
- 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.
- Can I use it commercially?
- Yes, with conditions. LGPL-2.1 is a weak copyleft licence: you can use it inside commercial and closed-source software, but if you distribute changes to its own files, you must publish those changes under the same licence.
- Is it still maintained?
- Yes. The repository last received commits 5 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on November 7, 2026, and from our analysis. They are not legal advice.
Star history
- pycountry/pycountry976 ★ · -1
| Repository | 2026-10-21 | 2026-11-07 |
|---|---|---|
| pycountry/pycountry | 977 | 976 |
-1 Stars in the last 17 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
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.
>>> import pycountry
>>> len(pycountry.countries)
249A 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.
>>> 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.
>>> 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.
>>> len(pycountry.subdivisions.get(country_code='DE'))
16Some 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.
>>> 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.
>>> 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.
[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.
Editorial 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.
Frequently asked questions
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.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/pycountry-pycountry)Star history chart for your README
[](https://hysenlabs.com/projects/pycountry-pycountry)Was this useful?