python-iso639 2025.1.28__tar.gz → 2025.2.18__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 (23) hide show
  1. {python_iso639-2025.1.28 → python_iso639-2025.2.18}/CHANGELOG.md +11 -0
  2. {python_iso639-2025.1.28/src/python_iso639.egg-info → python_iso639-2025.2.18}/PKG-INFO +32 -7
  3. {python_iso639-2025.1.28 → python_iso639-2025.2.18}/README.md +29 -4
  4. {python_iso639-2025.1.28 → python_iso639-2025.2.18}/pyproject.toml +4 -4
  5. {python_iso639-2025.1.28 → python_iso639-2025.2.18}/src/iso639/language.py +72 -16
  6. python_iso639-2025.2.18/src/iso639/py.typed +0 -0
  7. {python_iso639-2025.1.28 → python_iso639-2025.2.18/src/python_iso639.egg-info}/PKG-INFO +32 -7
  8. {python_iso639-2025.1.28 → python_iso639-2025.2.18}/src/python_iso639.egg-info/SOURCES.txt +1 -0
  9. {python_iso639-2025.1.28 → python_iso639-2025.2.18}/src/python_iso639.egg-info/requires.txt +2 -2
  10. {python_iso639-2025.1.28 → python_iso639-2025.2.18}/tests/test_language.py +25 -9
  11. {python_iso639-2025.1.28 → python_iso639-2025.2.18}/LICENSE.txt +0 -0
  12. {python_iso639-2025.1.28 → python_iso639-2025.2.18}/MANIFEST.in +0 -0
  13. {python_iso639-2025.1.28 → python_iso639-2025.2.18}/setup.cfg +0 -0
  14. {python_iso639-2025.1.28 → python_iso639-2025.2.18}/src/iso639/__init__.py +0 -0
  15. {python_iso639-2025.1.28 → python_iso639-2025.2.18}/src/iso639/_data/__init__.py +0 -0
  16. {python_iso639-2025.1.28 → python_iso639-2025.2.18}/src/iso639/_data/iso-639-3-macrolanguages.tab +0 -0
  17. {python_iso639-2025.1.28 → python_iso639-2025.2.18}/src/iso639/_data/iso-639-3.tab +0 -0
  18. {python_iso639-2025.1.28 → python_iso639-2025.2.18}/src/iso639/_data/iso-639-3_Name_Index.tab +0 -0
  19. {python_iso639-2025.1.28 → python_iso639-2025.2.18}/src/iso639/_data/iso-639-3_Retirements.tab +0 -0
  20. {python_iso639-2025.1.28 → python_iso639-2025.2.18}/src/python_iso639.egg-info/dependency_links.txt +0 -0
  21. {python_iso639-2025.1.28 → python_iso639-2025.2.18}/src/python_iso639.egg-info/not-zip-safe +0 -0
  22. {python_iso639-2025.1.28 → python_iso639-2025.2.18}/src/python_iso639.egg-info/top_level.txt +0 -0
  23. {python_iso639-2025.1.28 → python_iso639-2025.2.18}/tests/test_version_number.py +0 -0
@@ -17,6 +17,17 @@ major/minor/micro version numbers like `05` (it'd have to be just `5`).
17
17
  ### Fixed
18
18
  ### Security
19
19
 
20
+ ## [2025.2.18]
21
+
22
+ ### Added
23
+ * `Language.match` now supports case-insensitive matching
24
+ and ignores leading/trailing whitespace by default.
25
+
26
+ ## [2025.2.8]
27
+
28
+ ### Added
29
+ * Explicitly indicated that the library is type-annotated.
30
+
20
31
  ## [2025.1.28]
21
32
 
22
33
  ### Changed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: python-iso639
3
- Version: 2025.1.28
3
+ Version: 2025.2.18
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
@@ -28,10 +28,10 @@ Requires-Python: >=3.9
28
28
  Description-Content-Type: text/markdown
29
29
  License-File: LICENSE.txt
30
30
  Provides-Extra: dev
31
- Requires-Dist: black==24.10.0; extra == "dev"
31
+ Requires-Dist: black==25.1.0; extra == "dev"
32
32
  Requires-Dist: build==1.2.2; extra == "dev"
33
33
  Requires-Dist: flake8==7.1.1; extra == "dev"
34
- Requires-Dist: mypy==1.14.1; extra == "dev"
34
+ Requires-Dist: mypy==1.15.0; extra == "dev"
35
35
  Requires-Dist: pytest==8.3.4; extra == "dev"
36
36
  Requires-Dist: requests==2.32.3; extra == "dev"
37
37
  Requires-Dist: twine==6.1.0; extra == "dev"
@@ -48,9 +48,10 @@ other associated information.
48
48
 
49
49
  Current features:
50
50
 
51
- * A representation of languages mapped across ISO 639-1, 639-2, and 639-3.
52
- * Functionality to "guess" what a language is for a given
51
+ * 🌐 A representation of languages mapped across ISO 639-1, 639-2, and 639-3.
52
+ * 🔎 Functionality to "guess" what a language is for a given
53
53
  unknown language code or name.
54
+ * 🚀 Optimized for speed in retrieving language information.
54
55
 
55
56
  ## Installation
56
57
 
@@ -82,6 +83,15 @@ Create a `Language` instance by one of the class methods.
82
83
  Language(part3='fra', part2b='fre', part2t='fra', part1='fr', scope='I', type='L', name='French', comment=None, other_names=None, macrolanguage=None, retire_reason=None, retire_change_to=None, retire_remedy=None, retire_date=None)
83
84
  ```
84
85
 
86
+ Fast object instantiation for retrieving language information (run on Python 3.13, macOS 15.3.1, Apple M1 Pro)
87
+
88
+ ```python
89
+ In [1]: import iso639
90
+
91
+ In [2]: %timeit iso639.Language.from_part3("fra")
92
+ 217 ns ± 0.139 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
93
+ ```
94
+
85
95
  #### From Another ISO 639 Code Set or a Reference Name
86
96
 
87
97
  ```python
@@ -144,11 +154,27 @@ Use the `match` classmethod:
144
154
  True
145
155
  ```
146
156
 
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`:
160
+
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
166
+ True
167
+ >>> iso639.Language.match("french", exact=True)
168
+ Traceback (most recent call last):
169
+ File "<stdin>", line 1, in <module>
170
+ LanguageNotFoundError: 'french' isn't an ISO language code or name
171
+ ```
172
+
147
173
  The classmethod `match` is particularly useful for consistently
148
174
  accessing a specific attribute from unknown inputs, e.g., the ISO 639-3 code.
149
175
 
150
176
  ```python
151
- >>> 'fra' == lang1.part3 == lang2.part3 == lang3.part3 == lang4.part3
177
+ >>> 'fra' == lang1.part3 == lang2.part3 == lang3.part3 == lang4.part3 == lang5.part3 == lang6.part3 == lang7.part3
152
178
  True
153
179
  ```
154
180
 
@@ -251,7 +277,6 @@ Beyond that, the precise order in matching is as follows:
251
277
  * ISO 639-3 alternative language names (the "print" ones)
252
278
  * ISO 639-3 alternative language names (the "inverted" ones)
253
279
 
254
- Only exact matching is done (there's no fuzzy string matching of any sort).
255
280
  As soon as a match is found, `Language.match` returns a `Language` instance.
256
281
  If there isn't a match, a `LanguageNotFoundError` is raised.
257
282
 
@@ -10,9 +10,10 @@ other associated information.
10
10
 
11
11
  Current features:
12
12
 
13
- * A representation of languages mapped across ISO 639-1, 639-2, and 639-3.
14
- * Functionality to "guess" what a language is for a given
13
+ * 🌐 A representation of languages mapped across ISO 639-1, 639-2, and 639-3.
14
+ * 🔎 Functionality to "guess" what a language is for a given
15
15
  unknown language code or name.
16
+ * 🚀 Optimized for speed in retrieving language information.
16
17
 
17
18
  ## Installation
18
19
 
@@ -44,6 +45,15 @@ Create a `Language` instance by one of the class methods.
44
45
  Language(part3='fra', part2b='fre', part2t='fra', part1='fr', scope='I', type='L', name='French', comment=None, other_names=None, macrolanguage=None, retire_reason=None, retire_change_to=None, retire_remedy=None, retire_date=None)
45
46
  ```
46
47
 
48
+ Fast object instantiation for retrieving language information (run on Python 3.13, macOS 15.3.1, Apple M1 Pro)
49
+
50
+ ```python
51
+ In [1]: import iso639
52
+
53
+ In [2]: %timeit iso639.Language.from_part3("fra")
54
+ 217 ns ± 0.139 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
55
+ ```
56
+
47
57
  #### From Another ISO 639 Code Set or a Reference Name
48
58
 
49
59
  ```python
@@ -106,11 +116,27 @@ Use the `match` classmethod:
106
116
  True
107
117
  ```
108
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`:
122
+
123
+ ```python
124
+ >>> lang5 = iso639.Language.match('FRA')
125
+ >>> lang6 = iso639.Language.match('fra ')
126
+ >>> lang7 = iso639.Language.match('french')
127
+ >>> lang4 == lang5 == lang6 == lang7
128
+ True
129
+ >>> iso639.Language.match("french", exact=True)
130
+ Traceback (most recent call last):
131
+ File "<stdin>", line 1, in <module>
132
+ LanguageNotFoundError: 'french' isn't an ISO language code or name
133
+ ```
134
+
109
135
  The classmethod `match` is particularly useful for consistently
110
136
  accessing a specific attribute from unknown inputs, e.g., the ISO 639-3 code.
111
137
 
112
138
  ```python
113
- >>> 'fra' == lang1.part3 == lang2.part3 == lang3.part3 == lang4.part3
139
+ >>> 'fra' == lang1.part3 == lang2.part3 == lang3.part3 == lang4.part3 == lang5.part3 == lang6.part3 == lang7.part3
114
140
  True
115
141
  ```
116
142
 
@@ -213,7 +239,6 @@ Beyond that, the precise order in matching is as follows:
213
239
  * ISO 639-3 alternative language names (the "print" ones)
214
240
  * ISO 639-3 alternative language names (the "inverted" ones)
215
241
 
216
- Only exact matching is done (there's no fuzzy string matching of any sort).
217
242
  As soon as a match is found, `Language.match` returns a `Language` instance.
218
243
  If there isn't a match, a `LanguageNotFoundError` is raised.
219
244
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "python-iso639"
7
- version = "2025.1.28"
7
+ version = "2025.2.18"
8
8
  description = "ISO 639 language codes, names, and other associated information"
9
9
  readme = "README.md"
10
10
  requires-python = ">= 3.9"
@@ -34,10 +34,10 @@ classifiers = [
34
34
 
35
35
  [project.optional-dependencies]
36
36
  dev = [
37
- "black == 24.10.0",
37
+ "black == 25.1.0",
38
38
  "build == 1.2.2",
39
39
  "flake8 == 7.1.1",
40
- "mypy == 1.14.1",
40
+ "mypy == 1.15.0",
41
41
  "pytest == 8.3.4",
42
42
  "requests == 2.32.3",
43
43
  "twine == 6.1.0",
@@ -52,7 +52,7 @@ where = [ "src" ]
52
52
  [tool.setuptools]
53
53
  zip-safe = false
54
54
  package-dir = { "" = "src" }
55
- package-data = { "iso639" = ["_data/*.tab"] }
55
+ package-data = { "iso639" = ["_data/*.tab", "py.typed"] }
56
56
 
57
57
  [tool.pytest.ini_options]
58
58
  addopts = "-vv --durations=0 --strict-markers"
@@ -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, Union, Set
8
+ from typing import Dict, List, NoReturn, Optional, Union, Set
9
9
 
10
10
  from ._data import (
11
11
  _PART3_TO_CODES,
@@ -26,6 +26,12 @@ 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
+ ]
33
+
34
+
29
35
  class LanguageNotFoundError(Exception):
30
36
  pass
31
37
 
@@ -100,13 +106,17 @@ class Language:
100
106
  return isinstance(other, Language) and self.part3 == other.part3
101
107
 
102
108
  @classmethod
103
- def match(cls, user_input: str, /) -> Language:
109
+ def match(cls, user_input: str, /, *, exact: bool = False) -> Language:
104
110
  """Return a ``Language`` instance by matching on the user input.
105
111
 
106
112
  Parameters
107
113
  ----------
108
114
  user_input : str
109
115
  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.
110
120
 
111
121
  Returns
112
122
  -------
@@ -140,29 +150,29 @@ class Language:
140
150
  _NameIndexColumn.PRINT_NAME,
141
151
  _NameIndexColumn.INVERTED_NAME,
142
152
  ]
143
- return _PART3_TO_LANGUAGES[_get_part3(user_input, query_order)]
153
+ return _PART3_TO_LANGUAGES[_get_part3(user_input, query_order, exact)]
144
154
 
145
155
  @classmethod
146
156
  def from_part3(cls, user_input: str, /) -> Language:
147
157
  """Return a ``Language`` instance from an ISO 639-3 code."""
148
158
  return _PART3_TO_LANGUAGES[
149
- _get_part3(user_input, [_CodesColumn.ID, _RetirementsColumn.ID])
159
+ _get_part3_exact(user_input, [_CodesColumn.ID, _RetirementsColumn.ID])
150
160
  ]
151
161
 
152
162
  @classmethod
153
163
  def from_part2b(cls, user_input: str, /) -> Language:
154
164
  """Return a ``Language`` instance from an ISO 639-2 (bibliographic) code."""
155
- return _PART3_TO_LANGUAGES[_get_part3(user_input, [_CodesColumn.PART2B])]
165
+ return _PART3_TO_LANGUAGES[_get_part3_exact(user_input, [_CodesColumn.PART2B])]
156
166
 
157
167
  @classmethod
158
168
  def from_part2t(cls, user_input: str, /) -> Language:
159
169
  """Return a ``Language`` instance from an ISO 639-2 (terminological) code."""
160
- return _PART3_TO_LANGUAGES[_get_part3(user_input, [_CodesColumn.PART2T])]
170
+ return _PART3_TO_LANGUAGES[_get_part3_exact(user_input, [_CodesColumn.PART2T])]
161
171
 
162
172
  @classmethod
163
173
  def from_part1(cls, user_input: str, /) -> Language:
164
174
  """Return a ``Language`` instance from an ISO 639-1 code."""
165
- return _PART3_TO_LANGUAGES[_get_part3(user_input, [_CodesColumn.PART1])]
175
+ return _PART3_TO_LANGUAGES[_get_part3_exact(user_input, [_CodesColumn.PART1])]
166
176
 
167
177
  @classmethod
168
178
  def from_name(cls, user_input: str, /) -> Language:
@@ -172,10 +182,57 @@ class Language:
172
182
  _NameIndexColumn.PRINT_NAME,
173
183
  _NameIndexColumn.INVERTED_NAME,
174
184
  ]
175
- return _PART3_TO_LANGUAGES[_get_part3(user_input, query_order)]
185
+ return _PART3_TO_LANGUAGES[_get_part3_exact(user_input, query_order)]
186
+
176
187
 
188
+ def _raise_language_not_found_error(user_input: str) -> NoReturn:
189
+ raise LanguageNotFoundError(f"{user_input!r} isn't an ISO language code or name")
190
+
191
+
192
+ def _get_part3(
193
+ user_input: str, query_order: List[_COLUMN_TYPE], exact: bool = True
194
+ ) -> str:
195
+ """Get the part 3 code of a language.
177
196
 
178
- def _get_part3(user_input: str, query_order: List[_COLUMN_TYPE]) -> str:
197
+ Parameters
198
+ ----------
199
+ user_input : str
200
+ The user-provided language code or name.
201
+ query_order : List[_COLUMN_TYPE]
202
+ 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.
206
+
207
+ Returns
208
+ -------
209
+ str
210
+
211
+ Raises
212
+ ------
213
+ LanguageNotFoundError
214
+ If `part3` isn't a language name or code
215
+ """
216
+ try:
217
+ return _get_part3_exact(user_input, query_order)
218
+ except LanguageNotFoundError as e:
219
+ if exact:
220
+ raise e
221
+ else:
222
+ for func in _STRING_CLEANING_FUNCS:
223
+ try:
224
+ return _get_part3_exact(func(user_input), query_order, user_input)
225
+ except LanguageNotFoundError:
226
+ continue
227
+ else:
228
+ _raise_language_not_found_error(user_input)
229
+
230
+
231
+ def _get_part3_exact(
232
+ user_input: str,
233
+ query_order: List[_COLUMN_TYPE],
234
+ original_user_input: Optional[str] = None,
235
+ ) -> str:
179
236
  """Get the part 3 code of a language.
180
237
 
181
238
  Parameters
@@ -184,6 +241,9 @@ def _get_part3(user_input: str, query_order: List[_COLUMN_TYPE]) -> str:
184
241
  The user-provided language code or name.
185
242
  query_order : List[_COLUMN_TYPE]
186
243
  A list of columns to specify query order.
244
+ original_user_input : str, optional
245
+ The original user input. Default is `None`.
246
+ This argument is used when the user input has been cleaned.
187
247
 
188
248
  Returns
189
249
  -------
@@ -198,8 +258,7 @@ def _get_part3(user_input: str, query_order: List[_COLUMN_TYPE]) -> str:
198
258
  for column in query_order:
199
259
  if column == _CodesColumn.ID:
200
260
  if user_input in _PART3_TO_CODES:
201
- part3 = user_input
202
- break
261
+ return user_input
203
262
  elif column == _CodesColumn.PART2B:
204
263
  part3 = _PART2B_TO_PART3.get(user_input)
205
264
  elif column == _CodesColumn.PART2T:
@@ -208,8 +267,7 @@ def _get_part3(user_input: str, query_order: List[_COLUMN_TYPE]) -> str:
208
267
  part3 = _PART1_TO_PART3.get(user_input)
209
268
  elif column == _RetirementsColumn.ID:
210
269
  if user_input in _PART3_TO_RETIREMENTS:
211
- part3 = user_input
212
- break
270
+ return user_input
213
271
  elif column == _CodesColumn.REF_NAME:
214
272
  part3 = _REF_NAME_TO_PART3.get(user_input)
215
273
  elif column == _NameIndexColumn.PRINT_NAME:
@@ -222,9 +280,7 @@ def _get_part3(user_input: str, query_order: List[_COLUMN_TYPE]) -> str:
222
280
  break
223
281
 
224
282
  if part3 is None:
225
- raise LanguageNotFoundError(
226
- f"{user_input!r} isn't an ISO language code or name"
227
- )
283
+ _raise_language_not_found_error(original_user_input or user_input)
228
284
 
229
285
  return part3
230
286
 
File without changes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: python-iso639
3
- Version: 2025.1.28
3
+ Version: 2025.2.18
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
@@ -28,10 +28,10 @@ Requires-Python: >=3.9
28
28
  Description-Content-Type: text/markdown
29
29
  License-File: LICENSE.txt
30
30
  Provides-Extra: dev
31
- Requires-Dist: black==24.10.0; extra == "dev"
31
+ Requires-Dist: black==25.1.0; extra == "dev"
32
32
  Requires-Dist: build==1.2.2; extra == "dev"
33
33
  Requires-Dist: flake8==7.1.1; extra == "dev"
34
- Requires-Dist: mypy==1.14.1; extra == "dev"
34
+ Requires-Dist: mypy==1.15.0; extra == "dev"
35
35
  Requires-Dist: pytest==8.3.4; extra == "dev"
36
36
  Requires-Dist: requests==2.32.3; extra == "dev"
37
37
  Requires-Dist: twine==6.1.0; extra == "dev"
@@ -48,9 +48,10 @@ other associated information.
48
48
 
49
49
  Current features:
50
50
 
51
- * A representation of languages mapped across ISO 639-1, 639-2, and 639-3.
52
- * Functionality to "guess" what a language is for a given
51
+ * 🌐 A representation of languages mapped across ISO 639-1, 639-2, and 639-3.
52
+ * 🔎 Functionality to "guess" what a language is for a given
53
53
  unknown language code or name.
54
+ * 🚀 Optimized for speed in retrieving language information.
54
55
 
55
56
  ## Installation
56
57
 
@@ -82,6 +83,15 @@ Create a `Language` instance by one of the class methods.
82
83
  Language(part3='fra', part2b='fre', part2t='fra', part1='fr', scope='I', type='L', name='French', comment=None, other_names=None, macrolanguage=None, retire_reason=None, retire_change_to=None, retire_remedy=None, retire_date=None)
83
84
  ```
84
85
 
86
+ Fast object instantiation for retrieving language information (run on Python 3.13, macOS 15.3.1, Apple M1 Pro)
87
+
88
+ ```python
89
+ In [1]: import iso639
90
+
91
+ In [2]: %timeit iso639.Language.from_part3("fra")
92
+ 217 ns ± 0.139 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
93
+ ```
94
+
85
95
  #### From Another ISO 639 Code Set or a Reference Name
86
96
 
87
97
  ```python
@@ -144,11 +154,27 @@ Use the `match` classmethod:
144
154
  True
145
155
  ```
146
156
 
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`:
160
+
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
166
+ True
167
+ >>> iso639.Language.match("french", exact=True)
168
+ Traceback (most recent call last):
169
+ File "<stdin>", line 1, in <module>
170
+ LanguageNotFoundError: 'french' isn't an ISO language code or name
171
+ ```
172
+
147
173
  The classmethod `match` is particularly useful for consistently
148
174
  accessing a specific attribute from unknown inputs, e.g., the ISO 639-3 code.
149
175
 
150
176
  ```python
151
- >>> 'fra' == lang1.part3 == lang2.part3 == lang3.part3 == lang4.part3
177
+ >>> 'fra' == lang1.part3 == lang2.part3 == lang3.part3 == lang4.part3 == lang5.part3 == lang6.part3 == lang7.part3
152
178
  True
153
179
  ```
154
180
 
@@ -251,7 +277,6 @@ Beyond that, the precise order in matching is as follows:
251
277
  * ISO 639-3 alternative language names (the "print" ones)
252
278
  * ISO 639-3 alternative language names (the "inverted" ones)
253
279
 
254
- Only exact matching is done (there's no fuzzy string matching of any sort).
255
280
  As soon as a match is found, `Language.match` returns a `Language` instance.
256
281
  If there isn't a match, a `LanguageNotFoundError` is raised.
257
282
 
@@ -5,6 +5,7 @@ README.md
5
5
  pyproject.toml
6
6
  src/iso639/__init__.py
7
7
  src/iso639/language.py
8
+ src/iso639/py.typed
8
9
  src/iso639/_data/__init__.py
9
10
  src/iso639/_data/iso-639-3-macrolanguages.tab
10
11
  src/iso639/_data/iso-639-3.tab
@@ -1,9 +1,9 @@
1
1
 
2
2
  [dev]
3
- black==24.10.0
3
+ black==25.1.0
4
4
  build==1.2.2
5
5
  flake8==7.1.1
6
- mypy==1.14.1
6
+ mypy==1.15.0
7
7
  pytest==8.3.4
8
8
  requests==2.32.3
9
9
  twine==6.1.0
@@ -7,18 +7,34 @@ import pytest
7
7
 
8
8
 
9
9
  @pytest.mark.parametrize(
10
- "user_input, expected_part3",
10
+ "user_input, exact, expected_part3",
11
11
  [
12
- ("fra", "fra"),
13
- ("fre", "fra"),
14
- ("fr", "fra"),
15
- ("French", "fra"),
16
- ("Castilian", "spa"),
12
+ ("fra", True, "fra"),
13
+ ("fra", False, "fra"),
14
+ ("FRA", True, None),
15
+ ("FRA", False, "fra"),
16
+ (" FRA ", False, "fra"),
17
+ ("Fra", True, None),
18
+ ("Fra", False, "fra"),
19
+ (" Fra ", False, "fra"),
20
+ ("French", True, "fra"),
21
+ ("French", False, "fra"),
22
+ ("FRENCH", True, None),
23
+ ("FRENCH", False, "fra"),
24
+ (" FRENCH ", False, "fra"),
25
+ ("french", True, None),
26
+ ("french", False, "fra"),
27
+ (" french ", False, "fra"),
28
+ ("Castilian", True, "spa"),
17
29
  ],
18
30
  )
19
- def test_match(user_input, expected_part3):
20
- actual_part3 = Language.match(user_input).part3
21
- assert actual_part3 == expected_part3
31
+ def test_match(user_input, exact, expected_part3):
32
+ if expected_part3 is None:
33
+ with pytest.raises(LanguageNotFoundError):
34
+ Language.match(user_input, exact=exact)
35
+ else:
36
+ actual_part3 = Language.match(user_input, exact=exact).part3
37
+ assert actual_part3 == expected_part3
22
38
 
23
39
 
24
40
  def test_name():