python-iso639 2025.2.18__tar.gz → 2025.11.16__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. {python_iso639-2025.2.18 → python_iso639-2025.11.16}/CHANGELOG.md +22 -0
  2. {python_iso639-2025.2.18/src/python_iso639.egg-info → python_iso639-2025.11.16}/PKG-INFO +26 -21
  3. {python_iso639-2025.2.18 → python_iso639-2025.11.16}/README.md +14 -10
  4. {python_iso639-2025.2.18 → python_iso639-2025.11.16}/pyproject.toml +10 -10
  5. {python_iso639-2025.2.18 → python_iso639-2025.11.16}/src/iso639/__init__.py +1 -1
  6. {python_iso639-2025.2.18 → python_iso639-2025.11.16}/src/iso639/_data/__init__.py +16 -18
  7. {python_iso639-2025.2.18 → python_iso639-2025.11.16}/src/iso639/_data/iso-639-3.tab +14 -11
  8. {python_iso639-2025.2.18 → python_iso639-2025.11.16}/src/iso639/_data/iso-639-3_Name_Index.tab +27 -5
  9. {python_iso639-2025.2.18 → python_iso639-2025.11.16}/src/iso639/_data/iso-639-3_Retirements.tab +20 -22
  10. {python_iso639-2025.2.18 → python_iso639-2025.11.16}/src/iso639/language.py +40 -42
  11. {python_iso639-2025.2.18 → python_iso639-2025.11.16/src/python_iso639.egg-info}/PKG-INFO +26 -21
  12. python_iso639-2025.11.16/src/python_iso639.egg-info/requires.txt +9 -0
  13. {python_iso639-2025.2.18 → python_iso639-2025.11.16}/tests/test_language.py +36 -6
  14. python_iso639-2025.2.18/src/python_iso639.egg-info/requires.txt +0 -9
  15. {python_iso639-2025.2.18 → python_iso639-2025.11.16}/LICENSE.txt +0 -0
  16. {python_iso639-2025.2.18 → python_iso639-2025.11.16}/MANIFEST.in +0 -0
  17. {python_iso639-2025.2.18 → python_iso639-2025.11.16}/setup.cfg +0 -0
  18. {python_iso639-2025.2.18 → python_iso639-2025.11.16}/src/iso639/_data/iso-639-3-macrolanguages.tab +0 -0
  19. {python_iso639-2025.2.18 → python_iso639-2025.11.16}/src/iso639/py.typed +0 -0
  20. {python_iso639-2025.2.18 → python_iso639-2025.11.16}/src/python_iso639.egg-info/SOURCES.txt +0 -0
  21. {python_iso639-2025.2.18 → python_iso639-2025.11.16}/src/python_iso639.egg-info/dependency_links.txt +0 -0
  22. {python_iso639-2025.2.18 → python_iso639-2025.11.16}/src/python_iso639.egg-info/not-zip-safe +0 -0
  23. {python_iso639-2025.2.18 → python_iso639-2025.11.16}/src/python_iso639.egg-info/top_level.txt +0 -0
  24. {python_iso639-2025.2.18 → python_iso639-2025.11.16}/tests/test_version_number.py +0 -0
@@ -17,6 +17,28 @@ major/minor/micro version numbers like `05` (it'd have to be just `5`).
17
17
  ### Fixed
18
18
  ### Security
19
19
 
20
+ ## [2025.11.16]
21
+
22
+ ### Fixed
23
+ * Fixed README.md (shown on the PyPI page) for the change from `exact` to `strict_case`
24
+ in `Language.match`.
25
+
26
+ ## [2025.11.11]
27
+
28
+ ### Added
29
+ * Added support for Python 3.14.
30
+
31
+ ### Changed
32
+ * Updated the ISO 639 data from SIL to the latest 2025-10-15 release.
33
+ * Case-insensitive matching for `Language.match` is now opt-in.
34
+ The keyword argument has been renamed `strict_case` (from `exact` before),
35
+ defaulting to `True`.
36
+
37
+ ### Removed
38
+ * In `Language.match`, removed the keyword argument `exact`
39
+ (replaced by `strict_case`, defaulting to `True`).
40
+ * Dropped support for Python 3.9.
41
+
20
42
  ## [2025.2.18]
21
43
 
22
44
  ### Added
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: python-iso639
3
- Version: 2025.2.18
3
+ Version: 2025.11.16
4
4
  Summary: ISO 639 language codes, names, and other associated information
5
5
  Author-email: "Jackson L. Lee" <jacksonlunlee@gmail.com>
6
6
  License: Apache 2.0
@@ -15,26 +15,27 @@ Classifier: License :: OSI Approved :: Apache Software License
15
15
  Classifier: Operating System :: OS Independent
16
16
  Classifier: Programming Language :: Python :: 3
17
17
  Classifier: Programming Language :: Python :: 3 :: Only
18
- Classifier: Programming Language :: Python :: 3.9
19
18
  Classifier: Programming Language :: Python :: 3.10
20
19
  Classifier: Programming Language :: Python :: 3.11
21
20
  Classifier: Programming Language :: Python :: 3.12
22
21
  Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python :: 3.14
23
23
  Classifier: Topic :: Text Processing
24
24
  Classifier: Topic :: Text Processing :: General
25
25
  Classifier: Topic :: Text Processing :: Indexing
26
26
  Classifier: Topic :: Text Processing :: Linguistic
27
- Requires-Python: >=3.9
27
+ Requires-Python: >=3.10
28
28
  Description-Content-Type: text/markdown
29
29
  License-File: LICENSE.txt
30
30
  Provides-Extra: dev
31
- Requires-Dist: black==25.1.0; extra == "dev"
32
- Requires-Dist: build==1.2.2; extra == "dev"
33
- Requires-Dist: flake8==7.1.1; extra == "dev"
34
- Requires-Dist: mypy==1.15.0; extra == "dev"
35
- Requires-Dist: pytest==8.3.4; extra == "dev"
36
- Requires-Dist: requests==2.32.3; extra == "dev"
37
- Requires-Dist: twine==6.1.0; extra == "dev"
31
+ Requires-Dist: black==25.11.0; extra == "dev"
32
+ Requires-Dist: build==1.3.0; extra == "dev"
33
+ Requires-Dist: flake8==7.3.0; extra == "dev"
34
+ Requires-Dist: mypy==1.18.2; extra == "dev"
35
+ Requires-Dist: pytest==9.0.0; extra == "dev"
36
+ Requires-Dist: requests==2.32.5; extra == "dev"
37
+ Requires-Dist: twine==6.2.0; extra == "dev"
38
+ Dynamic: license-file
38
39
 
39
40
  # python-iso639
40
41
 
@@ -154,22 +155,26 @@ Use the `match` classmethod:
154
155
  True
155
156
  ```
156
157
 
157
- By default, the classmethod `match` supports case-insensitive matching
158
- and ignores leading/trailing whitespace.
159
- To enforce exact matching instead, pass in `exact=True`:
158
+ By default, the classmethod `match` is case-sensitive.
159
+ To ignore case instead, pass in `strict_case=False`:
160
160
 
161
161
  ```python
162
- >>> lang5 = iso639.Language.match('FRA')
163
- >>> lang6 = iso639.Language.match('fra ')
164
- >>> lang7 = iso639.Language.match('french')
165
- >>> lang4 == lang5 == lang6 == lang7
162
+ >>> lang5 = iso639.Language.match('FRA', strict_case=False)
163
+ >>> lang6 = iso639.Language.match('french', strict_case=False)
164
+ >>> lang4 == lang5 == lang6
166
165
  True
167
- >>> iso639.Language.match("french", exact=True)
166
+ >>> iso639.Language.match("french")
168
167
  Traceback (most recent call last):
169
168
  File "<stdin>", line 1, in <module>
170
169
  LanguageNotFoundError: 'french' isn't an ISO language code or name
171
170
  ```
172
171
 
172
+ > [!NOTE]
173
+ > Depending on your use case, ignoring case could potentially lead to matching issues,
174
+ where a language code might match an unintended language name (or vice versa),
175
+ e.g., conflating "igo" and "Igo", while there exist the ISO 639-3 code `ahl` for Igo and
176
+ the ISO 639-3 code `igo` for Isebe.
177
+
173
178
  The classmethod `match` is particularly useful for consistently
174
179
  accessing a specific attribute from unknown inputs, e.g., the ISO 639-3 code.
175
180
 
@@ -302,7 +307,7 @@ e.g., [`dataclasses.asdict`](https://docs.python.org/3/library/dataclasses.html#
302
307
  ```python
303
308
  >>> import iso639
304
309
  >>> iso639.DATA_LAST_UPDATED
305
- datetime.date(2025, 1, 15)
310
+ datetime.date(2025, 10, 15)
306
311
  ```
307
312
 
308
313
  * `ALL_LANGUAGES`: The list of all `Language` objects based on the included language code data
@@ -312,7 +317,7 @@ e.g., [`dataclasses.asdict`](https://docs.python.org/3/library/dataclasses.html#
312
317
  >>> type(iso639.ALL_LANGUAGES)
313
318
  <class 'set'>
314
319
  >>> len(iso639.ALL_LANGUAGES)
315
- 8307
320
+ 8311
316
321
  ```
317
322
 
318
323
  ## Links
@@ -116,22 +116,26 @@ Use the `match` classmethod:
116
116
  True
117
117
  ```
118
118
 
119
- By default, the classmethod `match` supports case-insensitive matching
120
- and ignores leading/trailing whitespace.
121
- To enforce exact matching instead, pass in `exact=True`:
119
+ By default, the classmethod `match` is case-sensitive.
120
+ To ignore case instead, pass in `strict_case=False`:
122
121
 
123
122
  ```python
124
- >>> lang5 = iso639.Language.match('FRA')
125
- >>> lang6 = iso639.Language.match('fra ')
126
- >>> lang7 = iso639.Language.match('french')
127
- >>> lang4 == lang5 == lang6 == lang7
123
+ >>> lang5 = iso639.Language.match('FRA', strict_case=False)
124
+ >>> lang6 = iso639.Language.match('french', strict_case=False)
125
+ >>> lang4 == lang5 == lang6
128
126
  True
129
- >>> iso639.Language.match("french", exact=True)
127
+ >>> iso639.Language.match("french")
130
128
  Traceback (most recent call last):
131
129
  File "<stdin>", line 1, in <module>
132
130
  LanguageNotFoundError: 'french' isn't an ISO language code or name
133
131
  ```
134
132
 
133
+ > [!NOTE]
134
+ > Depending on your use case, ignoring case could potentially lead to matching issues,
135
+ where a language code might match an unintended language name (or vice versa),
136
+ e.g., conflating "igo" and "Igo", while there exist the ISO 639-3 code `ahl` for Igo and
137
+ the ISO 639-3 code `igo` for Isebe.
138
+
135
139
  The classmethod `match` is particularly useful for consistently
136
140
  accessing a specific attribute from unknown inputs, e.g., the ISO 639-3 code.
137
141
 
@@ -264,7 +268,7 @@ e.g., [`dataclasses.asdict`](https://docs.python.org/3/library/dataclasses.html#
264
268
  ```python
265
269
  >>> import iso639
266
270
  >>> iso639.DATA_LAST_UPDATED
267
- datetime.date(2025, 1, 15)
271
+ datetime.date(2025, 10, 15)
268
272
  ```
269
273
 
270
274
  * `ALL_LANGUAGES`: The list of all `Language` objects based on the included language code data
@@ -274,7 +278,7 @@ e.g., [`dataclasses.asdict`](https://docs.python.org/3/library/dataclasses.html#
274
278
  >>> type(iso639.ALL_LANGUAGES)
275
279
  <class 'set'>
276
280
  >>> len(iso639.ALL_LANGUAGES)
277
- 8307
281
+ 8311
278
282
  ```
279
283
 
280
284
  ## Links
@@ -4,10 +4,10 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "python-iso639"
7
- version = "2025.2.18"
7
+ version = "2025.11.16"
8
8
  description = "ISO 639 language codes, names, and other associated information"
9
9
  readme = "README.md"
10
- requires-python = ">= 3.9"
10
+ requires-python = ">= 3.10"
11
11
  license = { text = "Apache 2.0" }
12
12
  authors = [ { name = "Jackson L. Lee", email = "jacksonlunlee@gmail.com" } ]
13
13
  keywords = ["ISO 639", "language codes", "languages", "linguistics"]
@@ -21,11 +21,11 @@ classifiers = [
21
21
  "Operating System :: OS Independent",
22
22
  "Programming Language :: Python :: 3",
23
23
  "Programming Language :: Python :: 3 :: Only",
24
- "Programming Language :: Python :: 3.9",
25
24
  "Programming Language :: Python :: 3.10",
26
25
  "Programming Language :: Python :: 3.11",
27
26
  "Programming Language :: Python :: 3.12",
28
27
  "Programming Language :: Python :: 3.13",
28
+ "Programming Language :: Python :: 3.14",
29
29
  "Topic :: Text Processing",
30
30
  "Topic :: Text Processing :: General",
31
31
  "Topic :: Text Processing :: Indexing",
@@ -34,13 +34,13 @@ classifiers = [
34
34
 
35
35
  [project.optional-dependencies]
36
36
  dev = [
37
- "black == 25.1.0",
38
- "build == 1.2.2",
39
- "flake8 == 7.1.1",
40
- "mypy == 1.15.0",
41
- "pytest == 8.3.4",
42
- "requests == 2.32.3",
43
- "twine == 6.1.0",
37
+ "black == 25.11.0",
38
+ "build == 1.3.0",
39
+ "flake8 == 7.3.0",
40
+ "mypy == 1.18.2",
41
+ "pytest == 9.0.0",
42
+ "requests == 2.32.5",
43
+ "twine == 6.2.0",
44
44
  ]
45
45
 
46
46
  [project.urls]
@@ -9,7 +9,7 @@ from .language import ALL_LANGUAGES, Language, LanguageNotFoundError
9
9
  # While DATA_LAST_UPDATED is strictly the date for the ISO 639-3 data release,
10
10
  # __version__ can be bumped for changes other than data updates.
11
11
  __version__ = version("python-iso639")
12
- DATA_LAST_UPDATED = datetime.date(2025, 1, 15)
12
+ DATA_LAST_UPDATED = datetime.date(2025, 10, 15)
13
13
 
14
14
 
15
15
  __all__ = [
@@ -5,8 +5,6 @@ import os
5
5
  from collections import defaultdict
6
6
  from enum import Enum
7
7
 
8
- from typing import Dict, List, Union
9
-
10
8
 
11
9
  class _CodesColumn(Enum):
12
10
  ID = "Id"
@@ -40,10 +38,10 @@ class _MacrolanguagesColumn(Enum):
40
38
  STATUS = "I_Status"
41
39
 
42
40
 
43
- _COLUMN_TYPE = Union[
44
- _CodesColumn, _NameIndexColumn, _RetirementsColumn, _MacrolanguagesColumn
45
- ]
46
- _ROW_TYPE = Dict[_COLUMN_TYPE, str]
41
+ _COLUMN_TYPE = (
42
+ _CodesColumn | _NameIndexColumn | _RetirementsColumn | _MacrolanguagesColumn
43
+ )
44
+ _ROW_TYPE = dict[_COLUMN_TYPE, str]
47
45
 
48
46
 
49
47
  class _Table(Enum):
@@ -62,56 +60,56 @@ _DATA_TSV_PATHS = {
62
60
  }
63
61
 
64
62
 
65
- def _load_tsv(table: _Table) -> List[_ROW_TYPE]:
63
+ def _load_tsv(table: _Table) -> list[_ROW_TYPE]:
66
64
  column = table.value
67
65
  with open(_DATA_TSV_PATHS[table], encoding="utf-8", newline="") as tsv_file:
68
66
  tsv_reader = csv.DictReader(tsv_file, delimiter="\t")
69
67
  return [{column(k): v for k, v in row.items()} for row in tsv_reader]
70
68
 
71
69
 
72
- _PART3_TO_CODES: Dict[str, _ROW_TYPE] = {
70
+ _PART3_TO_CODES: dict[str, _ROW_TYPE] = {
73
71
  row[_CodesColumn.ID]: row for row in _load_tsv(_Table.CODES)
74
72
  }
75
73
 
76
- _part3_to_name_index: defaultdict[str, List[_ROW_TYPE]] = defaultdict(list)
74
+ _part3_to_name_index: defaultdict[str, list[_ROW_TYPE]] = defaultdict(list)
77
75
  for row in _load_tsv(_Table.NAME_INDEX):
78
76
  _part3_to_name_index[row[_NameIndexColumn.ID]].append(row)
79
- _PART3_TO_NAME_INDEX: Dict[str, List[_ROW_TYPE]] = dict(_part3_to_name_index)
77
+ _PART3_TO_NAME_INDEX: dict[str, list[_ROW_TYPE]] = dict(_part3_to_name_index)
80
78
 
81
- _PART3_TO_RETIREMENTS: Dict[str, _ROW_TYPE] = {
79
+ _PART3_TO_RETIREMENTS: dict[str, _ROW_TYPE] = {
82
80
  row[_RetirementsColumn.ID]: row for row in _load_tsv(_Table.RETIREMENTS)
83
81
  }
84
- _PART3_TO_MACROLANGUAGES: Dict[str, _ROW_TYPE] = {
82
+ _PART3_TO_MACROLANGUAGES: dict[str, _ROW_TYPE] = {
85
83
  row[_MacrolanguagesColumn.ID]: row for row in _load_tsv(_Table.MACROLANGUAGES)
86
84
  }
87
85
 
88
- _PART2B_TO_PART3: Dict[str, str] = {
86
+ _PART2B_TO_PART3: dict[str, str] = {
89
87
  row[_CodesColumn.PART2B]: part3
90
88
  for part3, row in _PART3_TO_CODES.items()
91
89
  if row[_CodesColumn.PART2B]
92
90
  }
93
- _PART2T_TO_PART3: Dict[str, str] = {
91
+ _PART2T_TO_PART3: dict[str, str] = {
94
92
  row[_CodesColumn.PART2T]: part3
95
93
  for part3, row in _PART3_TO_CODES.items()
96
94
  if row[_CodesColumn.PART2T]
97
95
  }
98
- _PART1_TO_PART3: Dict[str, str] = {
96
+ _PART1_TO_PART3: dict[str, str] = {
99
97
  row[_CodesColumn.PART1]: part3
100
98
  for part3, row in _PART3_TO_CODES.items()
101
99
  if row[_CodesColumn.PART1]
102
100
  }
103
- _REF_NAME_TO_PART3: Dict[str, str] = {
101
+ _REF_NAME_TO_PART3: dict[str, str] = {
104
102
  row[_CodesColumn.REF_NAME]: part3
105
103
  for part3, row in _PART3_TO_CODES.items()
106
104
  if row[_CodesColumn.REF_NAME]
107
105
  }
108
- _PRINT_NAME_TO_PART3: Dict[str, str] = {
106
+ _PRINT_NAME_TO_PART3: dict[str, str] = {
109
107
  row[_NameIndexColumn.PRINT_NAME]: part3
110
108
  for part3, rows in _PART3_TO_NAME_INDEX.items()
111
109
  for row in rows
112
110
  if row[_NameIndexColumn.PRINT_NAME]
113
111
  }
114
- _INVERTED_NAME_TO_PART3: Dict[str, str] = {
112
+ _INVERTED_NAME_TO_PART3: dict[str, str] = {
115
113
  row[_NameIndexColumn.INVERTED_NAME]: part3
116
114
  for part3, rows in _PART3_TO_NAME_INDEX.items()
117
115
  for row in rows
@@ -1297,7 +1297,7 @@ cli I L Chakali
1297
1297
  clj I L Laitu Chin
1298
1298
  clk I L Idu-Mishmi
1299
1299
  cll I L Chala
1300
- clm I L Clallam
1300
+ clm I L Klallam
1301
1301
  clo I L Lowland Oaxaca Chontal
1302
1302
  cls I H Classical Sanskrit
1303
1303
  clt I L Lautu Chin
@@ -2325,7 +2325,7 @@ gyz I L Geji
2325
2325
  gza I L Ganza
2326
2326
  gzi I L Gazi
2327
2327
  gzn I L Gane
2328
- haa I L Han
2328
+ haa I L Hän
2329
2329
  hab I L Hanoi Sign Language
2330
2330
  hac I L Gurani
2331
2331
  had I L Hatam
@@ -2892,7 +2892,7 @@ kce I L Kaivi
2892
2892
  kcf I L Ukaan
2893
2893
  kcg I L Tyap
2894
2894
  kch I L Vono
2895
- kci I L Kamantan
2895
+ kci I L Ngyian
2896
2896
  kcj I L Kobiana
2897
2897
  kck I L Kalanga
2898
2898
  kcl I L Kela (Papua New Guinea)
@@ -3390,7 +3390,7 @@ kwg I L Sara Kaba Deme
3390
3390
  kwh I L Kowiai
3391
3391
  kwi I L Awa-Cuaiquer
3392
3392
  kwj I L Kwanga
3393
- kwk I L Kwakiutl
3393
+ kwk I L Kwak'wala
3394
3394
  kwl I L Kofyar
3395
3395
  kwm I L Kwambi
3396
3396
  kwn I L Kwangali
@@ -3795,7 +3795,7 @@ lup I L Lumbu
3795
3795
  luq I L Lucumi
3796
3796
  lur I L Laura
3797
3797
  lus lus lus I L Lushai
3798
- lut I L Lushootseed
3798
+ lut I E Lushootseed
3799
3799
  luu I L Lumba-Yakkha
3800
3800
  luv I L Luwati
3801
3801
  luw I L Luo (Cameroon)
@@ -4011,7 +4011,7 @@ mhj I L Mogholi
4011
4011
  mhk I L Mungaka
4012
4012
  mhl I L Mauwake
4013
4013
  mhm I L Makhuwa-Moniga
4014
- mhn I L Mócheno
4014
+ mhn I L Mòcheno
4015
4015
  mho I L Mashi (Zambia)
4016
4016
  mhp I L Balinese Malay
4017
4017
  mhq I L Mandan
@@ -4574,7 +4574,7 @@ nes I L Bhoti Kinnauri
4574
4574
  net I L Nete
4575
4575
  neu I C Neo
4576
4576
  nev I L Nyaheun
4577
- new new new I L Newari
4577
+ new new new I L Nepal Bhasa
4578
4578
  nex I L Neme
4579
4579
  ney I L Neyo
4580
4580
  nez I L Nez Perce
@@ -4916,7 +4916,7 @@ nxo I L Ndambomo
4916
4916
  nxq I L Naxi
4917
4917
  nxr I L Ninggerum
4918
4918
  nxx I L Nafri
4919
- nya nya nya ny I L Nyanja
4919
+ nya nya nya ny I L Chichewa
4920
4920
  nyb I L Nyangbo
4921
4921
  nyc I L Nyanga-li
4922
4922
  nyd I L Nyore
@@ -4954,6 +4954,7 @@ nzy I L Nzakambay
4954
4954
  nzz I L Nanga Dama Dogon
4955
4955
  oaa I L Orok
4956
4956
  oac I L Oroch
4957
+ oak I L Noakhali
4957
4958
  oar I H Old Aramaic (up to 700 BCE)
4958
4959
  oav I H Old Avar
4959
4960
  obi I E Obispeño
@@ -5017,6 +5018,7 @@ okv I L Orokaiva
5017
5018
  okx I L Okpe (Northwestern Edo)
5018
5019
  okz I H Old Khmer
5019
5020
  ola I L Walungge
5021
+ olb I L Oli-Bodiman
5020
5022
  old I L Mochi
5021
5023
  ole I L Olekha
5022
5024
  olk I E Olkol
@@ -5088,11 +5090,12 @@ ory I L Odia
5088
5090
  orz I L Ormu
5089
5091
  osa osa osa I L Osage
5090
5092
  osc I H Oscan
5093
+ osd I L Digor Ossetic
5091
5094
  osi I L Osing
5092
5095
  osn I H Old Sundanese
5093
5096
  oso I L Ososo
5094
5097
  osp I H Old Spanish
5095
- oss oss oss os I L Ossetian
5098
+ oss oss oss os I L Iron Ossetic
5096
5099
  ost I L Osatu
5097
5100
  osu I L Southern One
5098
5101
  osx I H Old Saxon
@@ -5758,6 +5761,7 @@ scu I L Shumcho
5758
5761
  scv I L Sheni
5759
5762
  scw I L Sha
5760
5763
  scx I H Sicel
5764
+ scz I L Shaetlan
5761
5765
  sda I L Toraja-Sa'dan
5762
5766
  sdb I L Shabak
5763
5767
  sdc I L Sassarese Sardinian
@@ -7651,7 +7655,6 @@ yob I E Yoba
7651
7655
  yog I L Yogad
7652
7656
  yoi I L Yonaguni
7653
7657
  yok I L Yokuts
7654
- yol I E Yola
7655
7658
  yom I L Yombe
7656
7659
  yon I L Yongkom
7657
7660
  yor yor yor yo I L Yoruba
@@ -7833,7 +7836,7 @@ zml I E Matngala
7833
7836
  zmm I L Marimanindji
7834
7837
  zmn I L Mbangwe
7835
7838
  zmo I L Molo
7836
- zmp I L Mpuono
7839
+ zmp I L Mbuun
7837
7840
  zmq I L Mituku
7838
7841
  zmr I L Maranunggu
7839
7842
  zms I L Mbesa
@@ -378,6 +378,7 @@ ary Moroccan Arabic Arabic, Moroccan
378
378
  arz Egyptian Arabic Arabic, Egyptian
379
379
  asa Asu (Tanzania) Asu (Tanzania)
380
380
  asb Assiniboine Assiniboine
381
+ asb Nakoda Assiniboine Nakoda Assiniboine
381
382
  asc Casuarina Coast Asmat Asmat, Casuarina Coast
382
383
  ase American Sign Language American Sign Language
383
384
  asf Auslan Auslan
@@ -413,6 +414,7 @@ ate Atemble Atemble
413
414
  atg Ivbie North-Okpela-Arhe Ivbie North-Okpela-Arhe
414
415
  ati Attié Attié
415
416
  atj Atikamekw Atikamekw
417
+ atj Nehirowimowin Nehirowimowin
416
418
  atk Ati Ati
417
419
  atl Mt. Iraya Agta Agta, Mt. Iraya
418
420
  atm Ata Ata
@@ -1340,6 +1342,7 @@ cky Cakfem-Mushere Cakfem-Mushere
1340
1342
  ckz Cakchiquel-Quiché Mixed Language Cakchiquel-Quiché Mixed Language
1341
1343
  cla Ron Ron
1342
1344
  clc Chilcotin Chilcotin
1345
+ clc Tsilhqot’in Tsilhqot’in
1343
1346
  cld Chaldean Neo-Aramaic Neo-Aramaic, Chaldean
1344
1347
  cle Lealao Chinantec Chinantec, Lealao
1345
1348
  clh Chilisso Chilisso
@@ -1348,6 +1351,7 @@ clj Laitu Chin Chin, Laitu
1348
1351
  clk Idu-Mishmi Idu-Mishmi
1349
1352
  cll Chala Chala
1350
1353
  clm Clallam Clallam
1354
+ clm Klallam Klallam
1351
1355
  clo Lowland Oaxaca Chontal Chontal, Lowland Oaxaca
1352
1356
  cls Classical Sanskrit Classical Sanskrit
1353
1357
  clt Lautu Chin Chin, Lautu
@@ -2432,7 +2436,7 @@ gyz Gyaazi Gyaazi
2432
2436
  gza Ganza Ganza
2433
2437
  gzi Gazi Gazi
2434
2438
  gzn Gane Gane
2435
- haa Han Han
2439
+ haa Hän Hän
2436
2440
  hab Hanoi Sign Language Hanoi Sign Language
2437
2441
  hac Gurani Gurani
2438
2442
  had Hatam Hatam
@@ -3042,6 +3046,7 @@ kcf Ukaan Ukaan
3042
3046
  kcg Tyap Tyap
3043
3047
  kch Vono Vono
3044
3048
  kci Kamantan Kamantan
3049
+ kci Ngyian Ngyian
3045
3050
  kcj Kobiana Kobiana
3046
3051
  kck Kalanga Kalanga
3047
3052
  kcl Kala Kala
@@ -3565,6 +3570,7 @@ kwh Kowiai Kowiai
3565
3570
  kwi Awa-Cuaiquer Awa-Cuaiquer
3566
3571
  kwj Kwanga Kwanga
3567
3572
  kwk Kwakiutl Kwakiutl
3573
+ kwk Kwak'wala Kwak'wala
3568
3574
  kwl Kofyar Kofyar
3569
3575
  kwm Kwambi Kwambi
3570
3576
  kwn Kwangali Kwangali
@@ -4217,7 +4223,7 @@ mhj Mogholi Mogholi
4217
4223
  mhk Mungaka Mungaka
4218
4224
  mhl Mauwake Mauwake
4219
4225
  mhm Makhuwa-Moniga Makhuwa-Moniga
4220
- mhn Mócheno Mócheno
4226
+ mhn Mòcheno Mòcheno
4221
4227
  mho Mashi (Zambia) Mashi (Zambia)
4222
4228
  mhp Balinese Malay Malay, Balinese
4223
4229
  mhq Mandan Mandan
@@ -4804,6 +4810,7 @@ net Nete Nete
4804
4810
  neu Neo Neo
4805
4811
  nev Nyaheun Nyaheun
4806
4812
  new Nepal Bhasa Bhasa, Nepal
4813
+ new Newar Newar
4807
4814
  new Newari Newari
4808
4815
  nex Neme Neme
4809
4816
  ney Neyo Neyo
@@ -5202,6 +5209,8 @@ nzy Nzakambay Nzakambay
5202
5209
  nzz Nanga Dama Dogon Dogon, Nanga Dama
5203
5210
  oaa Orok Orok
5204
5211
  oac Oroch Oroch
5212
+ oak Noakhailla Noakhailla
5213
+ oak Noakhali Noakhali
5205
5214
  oar Ancient Aramaic (up to 700 BCE) Aramaic, Ancient (up to 700 BCE)
5206
5215
  oar Old Aramaic (up to 700 BCE) Aramaic, Old (up to 700 BCE)
5207
5216
  oav Old Avar Avar, Old
@@ -5266,6 +5275,7 @@ okv Orokaiva Orokaiva
5266
5275
  okx Okpe (Northwestern Edo) Okpe (Northwestern Edo)
5267
5276
  okz Old Khmer Khmer, Old
5268
5277
  ola Walungge Walungge
5278
+ olb Oli-Bodiman Oli-Bodiman
5269
5279
  old Mochi Mochi
5270
5280
  ole Olekha Olekha
5271
5281
  olk Olkol Olkol
@@ -5338,10 +5348,16 @@ ory Oriya (individual language) Oriya (individual language)
5338
5348
  orz Ormu Ormu
5339
5349
  osa Osage Osage
5340
5350
  osc Oscan Oscan
5351
+ osd Digor Digor
5352
+ osd Digor Ossetian Ossetian, Digor
5353
+ osd Digor Ossetic Ossetic, Digor
5341
5354
  osi Osing Osing
5342
5355
  osn Old Sundanese Sundanese, Old
5343
5356
  oso Ososo Ososo
5344
5357
  osp Old Spanish Spanish, Old
5358
+ oss Iron Iron
5359
+ oss Iron Ossetian Ossetian, Iron
5360
+ oss Iron Ossetic Ossetic, Iron
5345
5361
  oss Ossetian Ossetian
5346
5362
  oss Ossetic Ossetic
5347
5363
  ost Osatu Osatu
@@ -5738,7 +5754,7 @@ pyx Pyu (Myanmar) Pyu (Myanmar)
5738
5754
  pyy Pyen Pyen
5739
5755
  pze Pesse Pesse
5740
5756
  pzh Pazeh Pazeh
5741
- pzn Jejara Naga Jejara Naga
5757
+ pzn Jejara Naga Naga, Jejara
5742
5758
  pzn Para Naga Naga, Para
5743
5759
  qua Quapaw Quapaw
5744
5760
  qub Huallaga Huánuco Quechua Quechua, Huallaga Huánuco
@@ -6043,6 +6059,7 @@ scu Shumcho Shumcho
6043
6059
  scv Sheni Sheni
6044
6060
  scw Sha Sha
6045
6061
  scx Sicel Sicel
6062
+ scz Shaetlan Shaetlan
6046
6063
  sda Toraja-Sa'dan Toraja-Sa'dan
6047
6064
  sdb Shabak Shabak
6048
6065
  sdc Sassarese Sardinian Sardinian, Sassarese
@@ -6066,6 +6083,7 @@ sdz Sallands Sallands
6066
6083
  sea Semai Semai
6067
6084
  seb Shempire Senoufo Senoufo, Shempire
6068
6085
  sec Sechelt Sechelt
6086
+ sec She shashishalhem She shashishalhem
6069
6087
  sed Sedang Sedang
6070
6088
  see Seneca Seneca
6071
6089
  sef Cebaara Senoufo Senoufo, Cebaara
@@ -6130,6 +6148,7 @@ sho Shanga Shanga
6130
6148
  shp Shipibo-Conibo Shipibo-Conibo
6131
6149
  shq Sala Sala
6132
6150
  shr Shi Shi
6151
+ shs Secwepemctsín Secwepemctsín
6133
6152
  shs Shuswap Shuswap
6134
6153
  sht Shasta Shasta
6135
6154
  shu Chadian Arabic Arabic, Chadian
@@ -6335,6 +6354,7 @@ sqq Sou Sou
6335
6354
  sqr Siculo Arabic Arabic, Siculo
6336
6355
  sqs Sri Lankan Sign Language Sri Lankan Sign Language
6337
6356
  sqt Soqotri Soqotri
6357
+ squ Sḵwx̱wú7mesh sníchim Sḵwx̱wú7mesh sníchim
6338
6358
  squ Squamish Squamish
6339
6359
  sqx Kufr Qassem Sign Language (KQSL) Kufr Qassem Sign Language (KQSL)
6340
6360
  sra Saruga Saruga
@@ -6668,11 +6688,14 @@ thk Tharaka Tharaka
6668
6688
  thl Dangaura Tharu Tharu, Dangaura
6669
6689
  thm Aheu Aheu
6670
6690
  thn Thachanadan Thachanadan
6691
+ thp Nłeʔkepmxcín Nłeʔkepmxcín
6671
6692
  thp Thompson Thompson
6693
+ thp Thompson River Salish Salish, Thompson River
6672
6694
  thq Kochila Tharu Tharu, Kochila
6673
6695
  thr Rana Tharu Tharu, Rana
6674
6696
  ths Thakali Thakali
6675
6697
  tht Tahltan Tahltan
6698
+ tht Tāłtān Tāłtān
6676
6699
  thu Thuri Thuri
6677
6700
  thv Tahaggart Tamahaq Tamahaq, Tahaggart
6678
6701
  thy Tha Tha
@@ -8011,7 +8034,6 @@ yob Yoba Yoba
8011
8034
  yog Yogad Yogad
8012
8035
  yoi Yonaguni Yonaguni
8013
8036
  yok Yokuts Yokuts
8014
- yol Yola Yola
8015
8037
  yom Yombe Yombe
8016
8038
  yon Yongkom Yongkom
8017
8039
  yor Yoruba Yoruba
@@ -8201,7 +8223,7 @@ zmm Marimanindji Marimanindji
8201
8223
  zmm Marramaninyshi Marramaninyshi
8202
8224
  zmn Mbangwe Mbangwe
8203
8225
  zmo Molo Molo
8204
- zmp Mpuono Mpuono
8226
+ zmp Mbuun Mbuun
8205
8227
  zmq Mituku Mituku
8206
8228
  zmr Maranunggu Maranunggu
8207
8229
  zms Mbesa Mbesa
@@ -1,5 +1,5 @@
1
1
  Id Ref_Name Ret_Reason Change_To Ret_Remedy Effective
2
- fri Western Frisian C fry 2007-02-01
2
+ fri Western Frisian C fry 2005-11-16
3
3
  auv Auvergnat M oci 2007-03-14
4
4
  gsc Gascon M oci 2007-03-14
5
5
  lms Limousin M oci 2007-03-14
@@ -18,7 +18,6 @@ mob Moinba S Split into five languages: Chug [cvg]; Lish [lsh]; Kalaktang Monp
18
18
  mzf Aiku S Split into four languages: Ambrak [aag]; Yangum Dey [yde]; Yangum Gel [ygl]; Yangum Mon [ymo] 2007-07-18
19
19
  nhj Tlalitzlipa Nahuatl M nhi 2007-07-18
20
20
  nhs Southeastern Puebla Nahuatl S Split into Sierra Negra Nahuatl [nsu] and Southeastern Puebla Nahuatl [npl] 2007-07-18
21
- occ Occidental D ile 2007-07-18
22
21
  tmx Tomyang M ybi 2007-07-18
23
22
  tot Patla-Chicontla Totonac S Split into Upper Necaxa Totonac [tku] and Tecpatlán Totonac [tcw] 2007-07-18
24
23
  xmi Miarrã N 2007-07-18
@@ -27,6 +26,7 @@ ztc Lachirioag Zapotec M zty 2007-07-18
27
26
  atf Atuence N 2007-08-10
28
27
  bqe Navarro-Labourdin Basque M eus 2007-08-10
29
28
  bsz Souletin Basque M eus 2007-08-10
29
+ occ Occidental D ile 2007-12-07
30
30
  aex Amerax M eng 2008-01-14
31
31
  ahe Ahe M knx 2008-01-14
32
32
  aiz Aari S Split into Aari [aiw] (new identifier) and Gayil [gyl] 2008-01-14
@@ -169,21 +169,21 @@ cmk Chimakum D xch 2010-01-18
169
169
  drh Darkhat M khk 2010-01-18
170
170
  drw Darwazi M prs 2010-01-18
171
171
  gav Gabutamon M dev 2010-01-18
172
- mof Mohegan-Montauk-Narragansett S split into Mohegan-Pequot [xpq] and Narragansett [xnt] 2010-01-18
172
+ mof Mohegan-Montauk-Narragansett S Split into Mohegan-Pequot [xpq] and Narragansett [xnt] 2010-01-18
173
173
  mst Cataelano Mandaya M mry 2010-01-18
174
174
  myt Sangab Mandaya M mry 2010-01-18
175
- rmr Caló S split into Caló [rmq] and Erromintxela [emx] 2010-01-18
176
- sgl Sanglechi-Ishkashimi S split into Sanglechi [sgy] and Ishkashimi [isk] 2010-01-18
175
+ rmr Caló S Split into Caló [rmq] and Erromintxela [emx] 2010-01-18
176
+ sgl Sanglechi-Ishkashimi S Split into Sanglechi [sgy] and Ishkashimi [isk] 2010-01-18
177
177
  sul Surigaonon S Split into Tandaganon [tgn] and Surigaonon [sgd] (new identifier) 2010-01-18
178
178
  sum Sumo-Mayangna S Split into Mayangna [yan] and Ulwa [ulw] 2010-01-18
179
179
  tnf Tangshewi M prs 2010-01-18
180
180
  wgw Wagawaga S Split into Yaleba [ylb] and Wagawaga [wgb] (new identifier) 2010-01-18
181
181
  ayx Ayi (China) D nun 2011-05-18
182
- bjq Southern Betsimisaraka Malagasy S split into Southern Betsimisaraka [bzc] and Tesaka Malagasy [tkg] 2011-05-18
182
+ bjq Southern Betsimisaraka Malagasy S Split into Southern Betsimisaraka [bzc] and Tesaka Malagasy [tkg] 2011-05-18
183
183
  dha Dhanwar (India) N 2011-05-18
184
- dkl Kolum So Dogon S split into Ampari Dogon [aqd] and Mombo Dogon [dmb] 2011-05-18
184
+ dkl Kolum So Dogon S Split into Ampari Dogon [aqd] and Mombo Dogon [dmb] 2011-05-18
185
185
  mja Mahei N 2011-05-18
186
- nbf Naxi S split into Naxi [nxq] and Narua [nru] 2011-05-18
186
+ nbf Naxi S Split into Naxi [nxq] and Narua [nru] 2011-05-18
187
187
  noo Nootka S Split into [dtd] Ditidaht and [nuk] Nuu-chah-nulth 2011-05-18
188
188
  tie Tingal M ras 2011-05-18
189
189
  tkk Takpa D twm 2011-05-18
@@ -195,15 +195,14 @@ dap Nisi (India) S Split into Nyishi [njz] and Tagin [tgj] 2012-02-03
195
195
  dwl Walo Kumbe Dogon S Split into Dogon, Bankan Tey (Walo) [dbw] and Dogon, Ben Tey (Beni) [dbt] 2012-02-03
196
196
  elp Elpaputih N 2012-02-03
197
197
  gbc Garawa S Split into Garrwa [wrk] and Wanyi [wny] 2012-02-03
198
- gio Gelao S Split into Qau [gqu] and A'ou [aou] with some going to Green Gelao [gig], some to Red Gelao [gir], and some to White Gelao [giw] 2012-02-03
198
+ gio Gelao S Split into Qau [gqu] and A'ou [aou] 2012-02-03
199
199
  hrr Horuru M jal 2012-02-03
200
200
  ibi Ibilo M opa 2012-02-03
201
- jar Jarawa (Nigeria) S split into Gwak [jgk] and Bankal [jjr] 2012-02-03
202
- kdv Kado S split into Kadu [zkd] and Kanan [zkn] 2012-02-03
201
+ jar Jarawa (Nigeria) S Split into Gwak [jgk] and Bankal [jjr] 2012-02-03
202
+ kdv Kado S Split into Kadu [zkd] and Kanan [zkn] 2012-02-03
203
203
  kgh Upper Tanudan Kalinga M kml 2012-02-03
204
204
  kpp Paku Karen S Split into Paku Karen [jkp] and Mobwa Karen [jkm] 2012-02-03
205
205
  kzh Kenuzi-Dongola S Split into Andaandi (Dongolawi) [dgl] and Kenzi (Mattoki) [xnz] 2012-02-03
206
- lcq Luhu M ppr 2012-02-03
207
206
  mgx Omati S Split into Barikewa [jbk] and Mouwase [jmw] 2012-02-03
208
207
  nln Durango Nahuatl S Split into Eastern Durango Nahuatl [azd] and Western Durango Nahuatl [azn] 2012-02-03
209
208
  pbz Palu N 2012-02-03
@@ -248,7 +247,6 @@ aue ǂKxʼauǁʼein D ktz 2015-01-12
248
247
  bmy Bemba (Democratic Republic of Congo) N 2015-01-12
249
248
  bxx Borna (Democratic Republic of Congo) N 2015-01-12
250
249
  byy Buya N 2015-01-12
251
- dzd Daza N 2015-01-12
252
250
  gfx Mangetti Dune ǃXung M vaj 2015-01-12
253
251
  gti Gbati-ri M nyc 2015-01-12
254
252
  ime Imeraguen N 2015-01-12
@@ -265,7 +263,6 @@ sgo Songa N 2015-01-12
265
263
  thx The D oyb 2015-01-12
266
264
  tsf Southwestern Tamang M taj 2015-01-12
267
265
  uok Uokha M ema 2015-01-12
268
- xsj Subi D suj 2015-01-12
269
266
  yds Yiddish Sign Language N 2015-01-12
270
267
  ymt Mator-Taygi-Karagas D mtm 2015-01-12
271
268
  ynh Yangho N 2015-01-12
@@ -276,9 +273,9 @@ cbh Cagua N 2016-01-15
276
273
  coy Coyaima M pij 2016-01-15
277
274
  cqu Chilean Quechua M quh 2016-01-15
278
275
  cum Cumeral N 2016-01-15
279
- duj Dhuwal S split into [dwu] Dhuwal and [dwy] Dhuwaya 2016-01-15
276
+ duj Dhuwal S Split into [dwu] Dhuwal and [dwy] Dhuwaya 2016-01-15
280
277
  ggn Eastern Gurung M gvr 2016-01-15
281
- ggo Southern Gondi S split into [esg] Aheri Gondi and [wsg] Adilabad Gondi 2016-01-15
278
+ ggo Southern Gondi S Split into [esg] Aheri Gondi and [wsg] Adilabad Gondi 2016-01-15
282
279
  guv Gey M duz 2016-01-15
283
280
  iap Iapama N 2016-01-15
284
281
  ill Iranun S Split into Iranun (Philippines) [ilp] and Iranun (Malaysia) [ilm] 2016-01-15
@@ -349,7 +346,7 @@ plp Palpa N 2020-01-23
349
346
  sdm Semandang S Split into Semandang [sdq], Beginci [ebc] and Gerai [gef] 2020-01-23
350
347
  tbb Tapeba N 2020-01-23
351
348
  xrq Karranga M dmw 2020-01-23
352
- xtz Tasmanian S Split into [xpv] Northern Tasman, [xph] North Midlands Tasman, [xpb] Northeastern Tasman, [xpd] Oyster Bay Tasmanian, [xpf] Southeast Tasma, [xpx] Southwestern Tasman, [xpw] Northwestern Tasman., [xpl] Port Sorell Tasman. and [xpz] Bruny Island Tasman. 2020-01-23
349
+ xtz Tasmanian S Split into [xpv] Northern Tasman, [xph] North Midlands Tasman, [xpb] Northeastern Tasman, [xpd] Oyster Bay Tasman, [xpf] Southeast Tasman, [xpx] Southwestern Tasman, [xpw] Northwestern Tasman, [xpl] Port Sorell Tasman, and [xpz] Bruny Island Tasman 2020-01-23
353
350
  zir Ziriya D scv 2020-01-23
354
351
  thw Thudam M ola 2020-03-19
355
352
  bic Bikaru M bir 2021-01-15
@@ -361,7 +358,7 @@ ngo Ngoni S Split into Ngoni (Tanzania) [xnj] and Ngoni (Mozambique) [xnq] 2021
361
358
  pat Papitalai M kxr 2021-01-15
362
359
  vki Ija-Zuba S Split into Koro Nulu [vkn] and Koro Zuba [vkz] 2021-01-15
363
360
  wra Warapu S Split into Bauni [bpe], Uni [uni] and Bouni [suo] 2021-01-15
364
- ajt Judeo-Tunisian Arabic D aeb 2022-01-20
361
+ ajt Judeo-Tunisian Arabic M aeb 2022-01-20
365
362
  cug Chungmboko S Split into Chung [cnq] and Mbuk [bpc] 2022-01-20
366
363
  lak Laka (Nigeria) M ksp 2022-01-20
367
364
  lno Lango (South Sudan) S Split into Lango [lgo], Imotong [imt], Logir [lqr] and Okolie [oie] 2022-01-20
@@ -373,7 +370,9 @@ wrd Warduji N 2022-01-20
373
370
  wya Wyandot S Split into Wyandot [wyn] and Wendat [wdt] 2022-01-20
374
371
  ajp South Levantine Arabic M apc 2023-01-20
375
372
  kgm Karipúna D plu 2023-01-20
373
+ ksa Shuwa-Zamani S Split into [rsw] Rishiwa and [izm] Kizamani 2023-01-20
376
374
  nom Nocamán M cbr 2023-01-20
375
+ plj Polci S Split into Pesse [pze], Nyamzak-Dir-Mbarimi [nzr], Zul [zlu], and Buli [uly] 2023-01-20
377
376
  pmk Pamlico M crr 2023-01-20
378
377
  prp Parsi M guj 2023-01-20
379
378
  slq Salchuq N 2023-01-20
@@ -383,7 +382,6 @@ tpw Tupí D tpn 2023-01-20
383
382
  xss Assan M zko 2023-01-20
384
383
  zkb Koibal D kjh 2023-01-20
385
384
  zua Zeem S Split into [tvi] Tulai, [dyr] Dyarim, [dsk] Dokshi, [cxh] Cha'ari, and [zem] Zeem 2023-01-20
386
- ksa Shuwa-Zamani S Split into [rsw] Rishiwa and [izm] Kizamani 2023-01-23
387
- plj Polci S Split into Pesse [pze], Nyamzak-Dir-Mbarimi [nzr], Zul [zlu], and Buli [uly] 2023-01-23
388
- dek Dek D sqm 2024-09-09
389
- nte Nathembo M eko 2024-09-09
385
+ dek Dek D sqm 2024-10-15
386
+ nte Nathembo M eko 2024-10-15
387
+ yol Yola M enm 2025-10-15
@@ -5,7 +5,7 @@ from __future__ import annotations
5
5
  import datetime
6
6
  from dataclasses import dataclass
7
7
 
8
- from typing import Dict, List, NoReturn, Optional, Union, Set
8
+ from typing import NoReturn
9
9
 
10
10
  from ._data import (
11
11
  _PART3_TO_CODES,
@@ -26,10 +26,7 @@ from ._data import (
26
26
  )
27
27
 
28
28
 
29
- _STRING_CLEANING_FUNCS = [
30
- lambda x: x.strip().lower(),
31
- lambda x: x.strip().title(),
32
- ]
29
+ _STRING_CLEANING_FUNCS = [lambda x: x.lower(), lambda x: x.title()]
33
30
 
34
31
 
35
32
  class LanguageNotFoundError(Exception):
@@ -73,31 +70,26 @@ class Language:
73
70
 
74
71
  # From the "codes" table
75
72
  part3: str
76
- # Although Union[..., None] and Optional[...] are equivalent, I prefer Union.
77
- # Optional simply doesn't sound right, as it would imply that the attribute in
78
- # question is optional, which it's not.
79
- # When support for Python 3.9 is dropped, we will switch to the pipe syntax
80
- # for `... | None`.
81
- part2b: Union[str, None]
82
- part2t: Union[str, None]
83
- part1: Union[str, None]
73
+ part2b: str | None
74
+ part2t: str | None
75
+ part1: str | None
84
76
  scope: str
85
- type: Union[str, None]
77
+ type: str | None
86
78
  status: str
87
79
  name: str
88
- comment: Union[str, None]
80
+ comment: str | None
89
81
 
90
82
  # From the "name_index" table
91
- other_names: Union[List[Name], None]
83
+ other_names: list[Name] | None
92
84
 
93
85
  # From the "macrolanguages" table
94
- macrolanguage: Union[str, None]
86
+ macrolanguage: str | None
95
87
 
96
88
  # From the "retirements" table
97
- retire_reason: Union[str, None]
98
- retire_change_to: Union[str, None]
99
- retire_remedy: Union[str, None]
100
- retire_date: Union[datetime.date, None]
89
+ retire_reason: str | None
90
+ retire_change_to: str | None
91
+ retire_remedy: str | None
92
+ retire_date: datetime.date | None
101
93
 
102
94
  def __hash__(self) -> int:
103
95
  return hash(self.part3)
@@ -106,17 +98,19 @@ class Language:
106
98
  return isinstance(other, Language) and self.part3 == other.part3
107
99
 
108
100
  @classmethod
109
- def match(cls, user_input: str, /, *, exact: bool = False) -> Language:
101
+ def match(cls, user_input: str, /, *, strict_case: bool = True) -> Language:
110
102
  """Return a ``Language`` instance by matching on the user input.
111
103
 
112
104
  Parameters
113
105
  ----------
114
106
  user_input : str
115
107
  A language code or name.
116
- exact : bool, optional
117
- Whether to enforce exact matching against the user input.
118
- Defaults to `False`. If `False`, matching is case-insensitive
119
- and ignores leading/trailing whitespace.
108
+ strict_case : bool, optional
109
+ Defaults to ``True``, for enforcing strict case sensitivity.
110
+ If ``False`` and if ``user_input`` doesn't find a match,
111
+ further match attempts will be made with the all-lowercase version of
112
+ ``user_input`` (``"foobar"``) and the title-case version of ``user_input``
113
+ (``"Foobar"``).
120
114
 
121
115
  Returns
122
116
  -------
@@ -140,7 +134,7 @@ class Language:
140
134
  # Order of columns to query the data tables.
141
135
  # Bias towards (and therefore prioritize) the user input being
142
136
  # a language code rather than a language name.
143
- query_order: List[_COLUMN_TYPE] = [
137
+ query_order: list[_COLUMN_TYPE] = [
144
138
  _CodesColumn.ID,
145
139
  _CodesColumn.PART2B,
146
140
  _CodesColumn.PART2T,
@@ -150,7 +144,7 @@ class Language:
150
144
  _NameIndexColumn.PRINT_NAME,
151
145
  _NameIndexColumn.INVERTED_NAME,
152
146
  ]
153
- return _PART3_TO_LANGUAGES[_get_part3(user_input, query_order, exact)]
147
+ return _PART3_TO_LANGUAGES[_get_part3(user_input, query_order, strict_case)]
154
148
 
155
149
  @classmethod
156
150
  def from_part3(cls, user_input: str, /) -> Language:
@@ -177,7 +171,7 @@ class Language:
177
171
  @classmethod
178
172
  def from_name(cls, user_input: str, /) -> Language:
179
173
  """Return a ``Language`` instance from an ISO 639-3 reference language name."""
180
- query_order: List[_COLUMN_TYPE] = [
174
+ query_order: list[_COLUMN_TYPE] = [
181
175
  _CodesColumn.REF_NAME,
182
176
  _NameIndexColumn.PRINT_NAME,
183
177
  _NameIndexColumn.INVERTED_NAME,
@@ -190,7 +184,7 @@ def _raise_language_not_found_error(user_input: str) -> NoReturn:
190
184
 
191
185
 
192
186
  def _get_part3(
193
- user_input: str, query_order: List[_COLUMN_TYPE], exact: bool = True
187
+ user_input: str, query_order: list[_COLUMN_TYPE], strict_case: bool = True
194
188
  ) -> str:
195
189
  """Get the part 3 code of a language.
196
190
 
@@ -200,9 +194,12 @@ def _get_part3(
200
194
  The user-provided language code or name.
201
195
  query_order : List[_COLUMN_TYPE]
202
196
  A list of columns to specify query order.
203
- exact : bool, optional
204
- Whether to enforce exact matching against the user input. Defaults to `True`.
205
- If `False`, basic string cleaning is applied to the user input.
197
+ strict_case : bool, optional
198
+ Defaults to ``True``, for enforcing strict case sensitivity.
199
+ If ``False`` and if ``user_input`` doesn't find a match,
200
+ further match attempts will be made with the all-lowercase version of
201
+ ``user_input`` (``"foobar"``) and the title-case version of ``user_input``
202
+ (``"Foobar"``).
206
203
 
207
204
  Returns
208
205
  -------
@@ -216,7 +213,7 @@ def _get_part3(
216
213
  try:
217
214
  return _get_part3_exact(user_input, query_order)
218
215
  except LanguageNotFoundError as e:
219
- if exact:
216
+ if strict_case:
220
217
  raise e
221
218
  else:
222
219
  for func in _STRING_CLEANING_FUNCS:
@@ -230,8 +227,8 @@ def _get_part3(
230
227
 
231
228
  def _get_part3_exact(
232
229
  user_input: str,
233
- query_order: List[_COLUMN_TYPE],
234
- original_user_input: Optional[str] = None,
230
+ query_order: list[_COLUMN_TYPE],
231
+ original_user_input: str | None = None,
235
232
  ) -> str:
236
233
  """Get the part 3 code of a language.
237
234
 
@@ -239,7 +236,7 @@ def _get_part3_exact(
239
236
  ----------
240
237
  user_input : str
241
238
  The user-provided language code or name.
242
- query_order : List[_COLUMN_TYPE]
239
+ query_order : list[_COLUMN_TYPE]
243
240
  A list of columns to specify query order.
244
241
  original_user_input : str, optional
245
242
  The original user input. Default is `None`.
@@ -254,7 +251,8 @@ def _get_part3_exact(
254
251
  LanguageNotFoundError
255
252
  If `part3` isn't a language name or code
256
253
  """
257
- part3: Union[str, None] = None
254
+ user_input = user_input.strip()
255
+ part3: str | None = None
258
256
  for column in query_order:
259
257
  if column == _CodesColumn.ID:
260
258
  if user_input in _PART3_TO_CODES:
@@ -307,7 +305,7 @@ def _get_language(part3: str) -> Language:
307
305
  else from_retirements[_RetirementsColumn.REF_NAME] # type: ignore
308
306
  )
309
307
 
310
- other_names: Union[List[Name], None] = []
308
+ other_names: list[Name] | None = []
311
309
  for row in _PART3_TO_NAME_INDEX.get(part3, []):
312
310
  p, i = row[_NameIndexColumn.PRINT_NAME], row[_NameIndexColumn.INVERTED_NAME]
313
311
  if not ref_name == p == i:
@@ -370,7 +368,7 @@ def _get_language(part3: str) -> Language:
370
368
  return language
371
369
 
372
370
 
373
- def _get_all_languages() -> Dict[str, Language]:
371
+ def _get_all_languages() -> dict[str, Language]:
374
372
  languages = {}
375
373
  for part3 in _PART3_TO_CODES:
376
374
  languages[part3] = _get_language(part3)
@@ -379,6 +377,6 @@ def _get_all_languages() -> Dict[str, Language]:
379
377
  return languages
380
378
 
381
379
 
382
- _PART3_TO_LANGUAGES: Dict[str, Language] = _get_all_languages()
380
+ _PART3_TO_LANGUAGES: dict[str, Language] = _get_all_languages()
383
381
 
384
- ALL_LANGUAGES: Set[Language] = set(_PART3_TO_LANGUAGES.values())
382
+ ALL_LANGUAGES: set[Language] = set(_PART3_TO_LANGUAGES.values())
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: python-iso639
3
- Version: 2025.2.18
3
+ Version: 2025.11.16
4
4
  Summary: ISO 639 language codes, names, and other associated information
5
5
  Author-email: "Jackson L. Lee" <jacksonlunlee@gmail.com>
6
6
  License: Apache 2.0
@@ -15,26 +15,27 @@ Classifier: License :: OSI Approved :: Apache Software License
15
15
  Classifier: Operating System :: OS Independent
16
16
  Classifier: Programming Language :: Python :: 3
17
17
  Classifier: Programming Language :: Python :: 3 :: Only
18
- Classifier: Programming Language :: Python :: 3.9
19
18
  Classifier: Programming Language :: Python :: 3.10
20
19
  Classifier: Programming Language :: Python :: 3.11
21
20
  Classifier: Programming Language :: Python :: 3.12
22
21
  Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python :: 3.14
23
23
  Classifier: Topic :: Text Processing
24
24
  Classifier: Topic :: Text Processing :: General
25
25
  Classifier: Topic :: Text Processing :: Indexing
26
26
  Classifier: Topic :: Text Processing :: Linguistic
27
- Requires-Python: >=3.9
27
+ Requires-Python: >=3.10
28
28
  Description-Content-Type: text/markdown
29
29
  License-File: LICENSE.txt
30
30
  Provides-Extra: dev
31
- Requires-Dist: black==25.1.0; extra == "dev"
32
- Requires-Dist: build==1.2.2; extra == "dev"
33
- Requires-Dist: flake8==7.1.1; extra == "dev"
34
- Requires-Dist: mypy==1.15.0; extra == "dev"
35
- Requires-Dist: pytest==8.3.4; extra == "dev"
36
- Requires-Dist: requests==2.32.3; extra == "dev"
37
- Requires-Dist: twine==6.1.0; extra == "dev"
31
+ Requires-Dist: black==25.11.0; extra == "dev"
32
+ Requires-Dist: build==1.3.0; extra == "dev"
33
+ Requires-Dist: flake8==7.3.0; extra == "dev"
34
+ Requires-Dist: mypy==1.18.2; extra == "dev"
35
+ Requires-Dist: pytest==9.0.0; extra == "dev"
36
+ Requires-Dist: requests==2.32.5; extra == "dev"
37
+ Requires-Dist: twine==6.2.0; extra == "dev"
38
+ Dynamic: license-file
38
39
 
39
40
  # python-iso639
40
41
 
@@ -154,22 +155,26 @@ Use the `match` classmethod:
154
155
  True
155
156
  ```
156
157
 
157
- By default, the classmethod `match` supports case-insensitive matching
158
- and ignores leading/trailing whitespace.
159
- To enforce exact matching instead, pass in `exact=True`:
158
+ By default, the classmethod `match` is case-sensitive.
159
+ To ignore case instead, pass in `strict_case=False`:
160
160
 
161
161
  ```python
162
- >>> lang5 = iso639.Language.match('FRA')
163
- >>> lang6 = iso639.Language.match('fra ')
164
- >>> lang7 = iso639.Language.match('french')
165
- >>> lang4 == lang5 == lang6 == lang7
162
+ >>> lang5 = iso639.Language.match('FRA', strict_case=False)
163
+ >>> lang6 = iso639.Language.match('french', strict_case=False)
164
+ >>> lang4 == lang5 == lang6
166
165
  True
167
- >>> iso639.Language.match("french", exact=True)
166
+ >>> iso639.Language.match("french")
168
167
  Traceback (most recent call last):
169
168
  File "<stdin>", line 1, in <module>
170
169
  LanguageNotFoundError: 'french' isn't an ISO language code or name
171
170
  ```
172
171
 
172
+ > [!NOTE]
173
+ > Depending on your use case, ignoring case could potentially lead to matching issues,
174
+ where a language code might match an unintended language name (or vice versa),
175
+ e.g., conflating "igo" and "Igo", while there exist the ISO 639-3 code `ahl` for Igo and
176
+ the ISO 639-3 code `igo` for Isebe.
177
+
173
178
  The classmethod `match` is particularly useful for consistently
174
179
  accessing a specific attribute from unknown inputs, e.g., the ISO 639-3 code.
175
180
 
@@ -302,7 +307,7 @@ e.g., [`dataclasses.asdict`](https://docs.python.org/3/library/dataclasses.html#
302
307
  ```python
303
308
  >>> import iso639
304
309
  >>> iso639.DATA_LAST_UPDATED
305
- datetime.date(2025, 1, 15)
310
+ datetime.date(2025, 10, 15)
306
311
  ```
307
312
 
308
313
  * `ALL_LANGUAGES`: The list of all `Language` objects based on the included language code data
@@ -312,7 +317,7 @@ e.g., [`dataclasses.asdict`](https://docs.python.org/3/library/dataclasses.html#
312
317
  >>> type(iso639.ALL_LANGUAGES)
313
318
  <class 'set'>
314
319
  >>> len(iso639.ALL_LANGUAGES)
315
- 8307
320
+ 8311
316
321
  ```
317
322
 
318
323
  ## Links
@@ -0,0 +1,9 @@
1
+
2
+ [dev]
3
+ black==25.11.0
4
+ build==1.3.0
5
+ flake8==7.3.0
6
+ mypy==1.18.2
7
+ pytest==9.0.0
8
+ requests==2.32.5
9
+ twine==6.2.0
@@ -7,9 +7,10 @@ import pytest
7
7
 
8
8
 
9
9
  @pytest.mark.parametrize(
10
- "user_input, exact, expected_part3",
10
+ "user_input, strict_case, expected_part3",
11
11
  [
12
12
  ("fra", True, "fra"),
13
+ (" fra ", True, "fra"),
13
14
  ("fra", False, "fra"),
14
15
  ("FRA", True, None),
15
16
  ("FRA", False, "fra"),
@@ -28,12 +29,38 @@ import pytest
28
29
  ("Castilian", True, "spa"),
29
30
  ],
30
31
  )
31
- def test_match(user_input, exact, expected_part3):
32
+ def test_match(user_input, strict_case, expected_part3):
32
33
  if expected_part3 is None:
33
34
  with pytest.raises(LanguageNotFoundError):
34
- Language.match(user_input, exact=exact)
35
+ Language.match(user_input, strict_case=strict_case)
35
36
  else:
36
- actual_part3 = Language.match(user_input, exact=exact).part3
37
+ actual_part3 = Language.match(user_input, strict_case=strict_case).part3
38
+ assert actual_part3 == expected_part3
39
+
40
+
41
+ @pytest.mark.parametrize(
42
+ "user_input, expected_part3",
43
+ [
44
+ ("fra", "fra"),
45
+ (" fra ", "fra"),
46
+ ("FRA", None),
47
+ (" FRA ", None),
48
+ ("Fra", None),
49
+ (" Fra ", None),
50
+ ("French", "fra"),
51
+ ("FRENCH", None),
52
+ (" FRENCH ", None),
53
+ ("french", None),
54
+ (" french ", None),
55
+ ("Castilian", "spa"),
56
+ ],
57
+ )
58
+ def test_match_no_strict_case_kwarg(user_input, expected_part3):
59
+ if expected_part3 is None:
60
+ with pytest.raises(LanguageNotFoundError):
61
+ Language.match(user_input)
62
+ else:
63
+ actual_part3 = Language.match(user_input).part3
37
64
  assert actual_part3 == expected_part3
38
65
 
39
66
 
@@ -105,12 +132,15 @@ def test_invalid_inputs():
105
132
 
106
133
 
107
134
  def test_data_last_updated():
108
- assert DATA_LAST_UPDATED == datetime.date(2025, 1, 15), "Need to update README.md"
135
+ assert DATA_LAST_UPDATED == datetime.date(2025, 10, 15), "Need to update README.md"
109
136
 
110
137
 
111
138
  def test_all_languages():
112
139
  assert type(ALL_LANGUAGES) is set
113
- assert len(ALL_LANGUAGES) == 8307, "Need to update README.md"
140
+ # Defining `len_all_languages` so that when the assertion fails,
141
+ # the error message is more informative.
142
+ len_all_languages = len(ALL_LANGUAGES)
143
+ assert len_all_languages == 8311, "Need to update README.md"
114
144
  lang = list(ALL_LANGUAGES)[0]
115
145
  assert type(lang) is Language
116
146
 
@@ -1,9 +0,0 @@
1
-
2
- [dev]
3
- black==25.1.0
4
- build==1.2.2
5
- flake8==7.1.1
6
- mypy==1.15.0
7
- pytest==8.3.4
8
- requests==2.32.3
9
- twine==6.1.0