Skip to content

Commit

Permalink
Merge pull request #24 from LBeaudoux/lang-kwargs
Browse files Browse the repository at this point in the history
Roll back Lang one positional argument constraint
  • Loading branch information
LBeaudoux authored Sep 29, 2024
2 parents 0ffae9a + eba63d0 commit 3e5f283
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 31 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ When an invalid language value is passed to `Lang`, an `InvalidLanguageValue` ex
... except InvalidLanguageValue as e:
... e.msg
...
"'foobar' is not a valid ISO 639 name or identifier."
"'foobar' is not a valid Lang argument."
```

When a deprecated language value is passed to `Lang`, a `DeprecatedLanguageValue` exception is raised.
Expand Down
23 changes: 15 additions & 8 deletions iso639/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
class InvalidLanguageValue(Exception):
"""Exception raised when the argument passed to the `Lang` constructor is
not a valid:
"""Exception raised when the arguments passed to the `Lang` constructor are
not valid and compatible:
- ISO 639-1 identifier
- ISO 639-2 English name
- ISO 639-2/B identifier
Expand All @@ -12,13 +12,20 @@ class InvalidLanguageValue(Exception):
- ISO 639-5 identifier
"""

def __init__(self, name_or_identifier):
def __init__(self, **kwargs):

self.invalid_value = name_or_identifier
self.msg = (
f"'{name_or_identifier}' is not a valid "
"ISO 639 name or identifier."
)
self.invalid_value = {
k: v
for k, v in kwargs.items()
if k == "name_or_identifier" or v is not None
}
if len(self.invalid_value) == 1:
main_arg = self.invalid_value["name_or_identifier"]
self.msg = f"{repr(main_arg)} is not a valid Lang argument."
else:
self.msg = (
f"**{self.invalid_value} are not valid Lang keyword arguments."
)

super().__init__(self.msg)

Expand Down
116 changes: 100 additions & 16 deletions iso639/iso639.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,71 @@ class Lang(tuple):

__slots__ = () # set immutability of Lang

def __new__(cls, name_or_identifier: Union[str, "Lang"]):
lang_tuple = cls._validate_arg(name_or_identifier)
if lang_tuple == tuple(): # not valid argument
cls._assert_not_deprecated(name_or_identifier)
raise InvalidLanguageValue(name_or_identifier=name_or_identifier)
def __new__(
cls,
name_or_identifier: Optional[Union[str, "Lang"]] = None,
name: Optional[str] = None,
pt1: Optional[str] = None,
pt2b: Optional[str] = None,
pt2t: Optional[str] = None,
pt3: Optional[str] = None,
pt5: Optional[str] = None,
):
# parse main argument
if name_or_identifier is None:
arg_lang_tuple = None
else:
arg_lang_tuple = cls._validate_arg(name_or_identifier)

# parse other arguments
if all(v is None for v in (name, pt1, pt2b, pt2t, pt3, pt5)):
kwargs_lang_tuple = None
else:
kwargs_lang_tuple = cls._validate_kwargs(
name=name, pt1=pt1, pt2b=pt2b, pt2t=pt2t, pt3=pt3, pt5=pt5
)

# check compatiblity between main argument and other arguments
if arg_lang_tuple is None and kwargs_lang_tuple is None:
lang_tuple = None
elif arg_lang_tuple is not None and kwargs_lang_tuple is None:
lang_tuple = arg_lang_tuple
elif kwargs_lang_tuple is not None and arg_lang_tuple is None:
lang_tuple = kwargs_lang_tuple
elif (
arg_lang_tuple is not None
and kwargs_lang_tuple is not None
and arg_lang_tuple == kwargs_lang_tuple
):
lang_tuple = arg_lang_tuple
else:
lang_tuple = tuple()

# instantiate as a tuple of ISO 639 language values
return tuple.__new__(cls, lang_tuple)
# chack if arguments match a deprecated language value
if lang_tuple == tuple():
cls._assert_not_deprecated(
name_or_identifier=name_or_identifier,
name=name,
pt1=pt1,
pt2b=pt2b,
pt2t=pt2t,
pt3=pt3,
pt5=pt5,
)

if not lang_tuple:
raise InvalidLanguageValue(
name_or_identifier=name_or_identifier,
name=name,
pt1=pt1,
pt2b=pt2b,
pt2t=pt2t,
pt3=pt3,
pt5=pt5,
)
else:
# instantiate as a tuple of ISO 639 language values
return tuple.__new__(cls, lang_tuple)

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

@classmethod
def _assert_not_deprecated(cls, arg_value):
for key in ("id", "name"):
try:
d = cls._deprecated[key][arg_value]
except KeyError:
pass
else:
d[key] = arg_value
raise DeprecatedLanguageValue(**d)
def _validate_kwargs(cls, **kwargs):
lang_tuples = set()
for tg, v in kwargs.items():
if v:
lang_tuples.add(cls._get_language_tuple(tg, v))
if len(lang_tuples) == 1:
return lang_tuples.pop()

return tuple()

@classmethod
def _assert_not_deprecated(cls, **kwargs):
deprecated = []
for kw, arg_value in kwargs.items():
if arg_value is None:
continue
elif kw == "name_or_identifier":
keys = ("id", "name")
elif kw == "name":
keys = ("name",)
elif kw in ("pt1", "pt2b", "pt2t", "pt3", "pt5"):
keys = ("id",)

for k in keys:
try:
d = cls._deprecated[k][arg_value]
except KeyError:
pass
else:
d[k] = arg_value
deprecated.append(d)

if deprecated and deprecated.count(deprecated[0]) == 1:
raise DeprecatedLanguageValue(**deprecated[0])

return True

@classmethod
def _get_language_tuple(cls, tag, arg_value):
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "iso639-lang"
version = "2.4.1"
version = "2.4.2"
description = "A fast, simple ISO 639 library."
keywords = ["ISO 639", "ISO 639-1", "ISO 639-2", "ISO 639-3", "ISO 639-5", "language code"]
readme = "README.md"
Expand Down
23 changes: 19 additions & 4 deletions tests/test_iso639.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,27 @@ def test_not_equal_languages_None(self):
assert lg1 != lg2

def test_multiple_args(self):
with pytest.raises(TypeError):
Lang("fra", "French", "fr", "fre", "fra", "fra") == Lang("French")

def test_wrong_multiple_args(self):
with pytest.raises(InvalidLanguageValue):
Lang("fra", "fr")

def test_one_kwarg(self):
assert Lang(pt1="fr") == Lang("fr")

def test_one_wrong_kwarg(self):
with pytest.raises(InvalidLanguageValue):
Lang(name="fr")

def test_mutliple_kwargs(self):
with pytest.raises(TypeError):
Lang(name_or_identifier="fr", pt3="fra")
Lang(
name="French", pt1="fr", pt2b="fre", pt2t="fra", pt3="fra"
) == Lang("French")

def test_mutliple_wrong_kwargs(self):
with pytest.raises(InvalidLanguageValue):
Lang(name="French", pt1="en")

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

def test_no_arg_no_kwarg(self):
with pytest.raises(TypeError):
with pytest.raises(InvalidLanguageValue):
Lang()

def test_none_arg(self):
Expand Down
2 changes: 1 addition & 1 deletion tests/test_readme_examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ def test_example_other_names():
def test_example_invalid_value():
with pytest.raises(InvalidLanguageValue) as exc_info:
Lang("foobar")
s = "'foobar' is not a valid ISO 639 name or identifier."
s = "'foobar' is not a valid Lang argument."
assert exc_info.value.msg == s


Expand Down

0 comments on commit 3e5f283

Please sign in to comment.