Skip to content

Commit 3e5f283

Browse files
authored
Merge pull request #24 from LBeaudoux/lang-kwargs
Roll back Lang one positional argument constraint
2 parents 0ffae9a + eba63d0 commit 3e5f283

File tree

6 files changed

+137
-31
lines changed

6 files changed

+137
-31
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ When an invalid language value is passed to `Lang`, an `InvalidLanguageValue` ex
162162
... except InvalidLanguageValue as e:
163163
... e.msg
164164
...
165-
"'foobar' is not a valid ISO 639 name or identifier."
165+
"'foobar' is not a valid Lang argument."
166166
```
167167

168168
When a deprecated language value is passed to `Lang`, a `DeprecatedLanguageValue` exception is raised.

iso639/exceptions.py

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
class InvalidLanguageValue(Exception):
2-
"""Exception raised when the argument passed to the `Lang` constructor is
3-
not a valid:
2+
"""Exception raised when the arguments passed to the `Lang` constructor are
3+
not valid and compatible:
44
- ISO 639-1 identifier
55
- ISO 639-2 English name
66
- ISO 639-2/B identifier
@@ -12,13 +12,20 @@ class InvalidLanguageValue(Exception):
1212
- ISO 639-5 identifier
1313
"""
1414

15-
def __init__(self, name_or_identifier):
15+
def __init__(self, **kwargs):
1616

17-
self.invalid_value = name_or_identifier
18-
self.msg = (
19-
f"'{name_or_identifier}' is not a valid "
20-
"ISO 639 name or identifier."
21-
)
17+
self.invalid_value = {
18+
k: v
19+
for k, v in kwargs.items()
20+
if k == "name_or_identifier" or v is not None
21+
}
22+
if len(self.invalid_value) == 1:
23+
main_arg = self.invalid_value["name_or_identifier"]
24+
self.msg = f"{repr(main_arg)} is not a valid Lang argument."
25+
else:
26+
self.msg = (
27+
f"**{self.invalid_value} are not valid Lang keyword arguments."
28+
)
2229

2330
super().__init__(self.msg)
2431

iso639/iso639.py

Lines changed: 100 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,71 @@ class Lang(tuple):
6363

6464
__slots__ = () # set immutability of Lang
6565

66-
def __new__(cls, name_or_identifier: Union[str, "Lang"]):
67-
lang_tuple = cls._validate_arg(name_or_identifier)
68-
if lang_tuple == tuple(): # not valid argument
69-
cls._assert_not_deprecated(name_or_identifier)
70-
raise InvalidLanguageValue(name_or_identifier=name_or_identifier)
66+
def __new__(
67+
cls,
68+
name_or_identifier: Optional[Union[str, "Lang"]] = None,
69+
name: Optional[str] = None,
70+
pt1: Optional[str] = None,
71+
pt2b: Optional[str] = None,
72+
pt2t: Optional[str] = None,
73+
pt3: Optional[str] = None,
74+
pt5: Optional[str] = None,
75+
):
76+
# parse main argument
77+
if name_or_identifier is None:
78+
arg_lang_tuple = None
79+
else:
80+
arg_lang_tuple = cls._validate_arg(name_or_identifier)
81+
82+
# parse other arguments
83+
if all(v is None for v in (name, pt1, pt2b, pt2t, pt3, pt5)):
84+
kwargs_lang_tuple = None
85+
else:
86+
kwargs_lang_tuple = cls._validate_kwargs(
87+
name=name, pt1=pt1, pt2b=pt2b, pt2t=pt2t, pt3=pt3, pt5=pt5
88+
)
89+
90+
# check compatiblity between main argument and other arguments
91+
if arg_lang_tuple is None and kwargs_lang_tuple is None:
92+
lang_tuple = None
93+
elif arg_lang_tuple is not None and kwargs_lang_tuple is None:
94+
lang_tuple = arg_lang_tuple
95+
elif kwargs_lang_tuple is not None and arg_lang_tuple is None:
96+
lang_tuple = kwargs_lang_tuple
97+
elif (
98+
arg_lang_tuple is not None
99+
and kwargs_lang_tuple is not None
100+
and arg_lang_tuple == kwargs_lang_tuple
101+
):
102+
lang_tuple = arg_lang_tuple
103+
else:
104+
lang_tuple = tuple()
71105

72-
# instantiate as a tuple of ISO 639 language values
73-
return tuple.__new__(cls, lang_tuple)
106+
# chack if arguments match a deprecated language value
107+
if lang_tuple == tuple():
108+
cls._assert_not_deprecated(
109+
name_or_identifier=name_or_identifier,
110+
name=name,
111+
pt1=pt1,
112+
pt2b=pt2b,
113+
pt2t=pt2t,
114+
pt3=pt3,
115+
pt5=pt5,
116+
)
117+
118+
if not lang_tuple:
119+
raise InvalidLanguageValue(
120+
name_or_identifier=name_or_identifier,
121+
name=name,
122+
pt1=pt1,
123+
pt2b=pt2b,
124+
pt2t=pt2t,
125+
pt3=pt3,
126+
pt5=pt5,
127+
)
128+
else:
129+
# instantiate as a tuple of ISO 639 language values
130+
return tuple.__new__(cls, lang_tuple)
74131

75132
def __repr__(self):
76133
chunks = ["=".join((tg, repr(getattr(self, tg)))) for tg in self._tags]
@@ -210,15 +267,42 @@ def _validate_arg(cls, arg_value):
210267
return tuple()
211268

212269
@classmethod
213-
def _assert_not_deprecated(cls, arg_value):
214-
for key in ("id", "name"):
215-
try:
216-
d = cls._deprecated[key][arg_value]
217-
except KeyError:
218-
pass
219-
else:
220-
d[key] = arg_value
221-
raise DeprecatedLanguageValue(**d)
270+
def _validate_kwargs(cls, **kwargs):
271+
lang_tuples = set()
272+
for tg, v in kwargs.items():
273+
if v:
274+
lang_tuples.add(cls._get_language_tuple(tg, v))
275+
if len(lang_tuples) == 1:
276+
return lang_tuples.pop()
277+
278+
return tuple()
279+
280+
@classmethod
281+
def _assert_not_deprecated(cls, **kwargs):
282+
deprecated = []
283+
for kw, arg_value in kwargs.items():
284+
if arg_value is None:
285+
continue
286+
elif kw == "name_or_identifier":
287+
keys = ("id", "name")
288+
elif kw == "name":
289+
keys = ("name",)
290+
elif kw in ("pt1", "pt2b", "pt2t", "pt3", "pt5"):
291+
keys = ("id",)
292+
293+
for k in keys:
294+
try:
295+
d = cls._deprecated[k][arg_value]
296+
except KeyError:
297+
pass
298+
else:
299+
d[k] = arg_value
300+
deprecated.append(d)
301+
302+
if deprecated and deprecated.count(deprecated[0]) == 1:
303+
raise DeprecatedLanguageValue(**deprecated[0])
304+
305+
return True
222306

223307
@classmethod
224308
def _get_language_tuple(cls, tag, arg_value):

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "iso639-lang"
7-
version = "2.4.1"
7+
version = "2.4.2"
88
description = "A fast, simple ISO 639 library."
99
keywords = ["ISO 639", "ISO 639-1", "ISO 639-2", "ISO 639-3", "ISO 639-5", "language code"]
1010
readme = "README.md"

tests/test_iso639.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,27 @@ def test_not_equal_languages_None(self):
4646
assert lg1 != lg2
4747

4848
def test_multiple_args(self):
49-
with pytest.raises(TypeError):
49+
Lang("fra", "French", "fr", "fre", "fra", "fra") == Lang("French")
50+
51+
def test_wrong_multiple_args(self):
52+
with pytest.raises(InvalidLanguageValue):
5053
Lang("fra", "fr")
5154

55+
def test_one_kwarg(self):
56+
assert Lang(pt1="fr") == Lang("fr")
57+
58+
def test_one_wrong_kwarg(self):
59+
with pytest.raises(InvalidLanguageValue):
60+
Lang(name="fr")
61+
5262
def test_mutliple_kwargs(self):
53-
with pytest.raises(TypeError):
54-
Lang(name_or_identifier="fr", pt3="fra")
63+
Lang(
64+
name="French", pt1="fr", pt2b="fre", pt2t="fra", pt3="fra"
65+
) == Lang("French")
66+
67+
def test_mutliple_wrong_kwargs(self):
68+
with pytest.raises(InvalidLanguageValue):
69+
Lang(name="French", pt1="en")
5570

5671
def test_kwarg_wrong_key(self):
5772
with pytest.raises(TypeError):
@@ -62,7 +77,7 @@ def test_kwarg_wrong_value(self):
6277
Lang(name_or_identifier="foobar")
6378

6479
def test_no_arg_no_kwarg(self):
65-
with pytest.raises(TypeError):
80+
with pytest.raises(InvalidLanguageValue):
6681
Lang()
6782

6883
def test_none_arg(self):

tests/test_readme_examples.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ def test_example_other_names():
138138
def test_example_invalid_value():
139139
with pytest.raises(InvalidLanguageValue) as exc_info:
140140
Lang("foobar")
141-
s = "'foobar' is not a valid ISO 639 name or identifier."
141+
s = "'foobar' is not a valid Lang argument."
142142
assert exc_info.value.msg == s
143143

144144

0 commit comments

Comments
 (0)