Skip to content

Commit

Permalink
Additionally validate words in other public methods.
Browse files Browse the repository at this point in the history
Doing so required expanding some of the interfaces to pass validation.
Blank words are no longer allowed.
  • Loading branch information
jaraco committed Jul 30, 2022
1 parent bc3d280 commit bc8143e
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 26 deletions.
12 changes: 11 additions & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
v5.7.0
v6.0.0
======

* #157: ``compare`` methods now validate their inputs
and will raise a more meaningful exception if an
empty string or None is passed. This expectation is now
documented.

* Many public methods now perform validation on arguments.
An empty string is no longer allowed for words or text.
Callers are expected to pass non-empty text or trap
the validation errors that are raised. The exceptions
raised are ``pydantic.error_wrappers.ValidationError``,
which are currently a subclass of ``ValueError``, but since
that
`may change <https://pydantic-docs.helpmanual.io/usage/validation_decorator/#validation-exception>`_,
tests check for a generic ``Exception``.

v5.6.2
======

Expand Down
60 changes: 42 additions & 18 deletions inflect/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,9 @@
Callable,
Sequence,
cast,
Any,
)
from numbers import Number


from pydantic import Field, validate_arguments
Expand Down Expand Up @@ -2038,6 +2040,7 @@ def __init__(self, orig) -> None:


Word = Annotated[str, Field(min_length=1)]
Falsish = Any # ideally, falsish would only validate on bool(value) is False


class engine:
Expand Down Expand Up @@ -2156,14 +2159,15 @@ def checkpatplural(self, pattern: str) -> None:
"""
return

def ud_match(self, word: str, wordlist: List[str]) -> Optional[str]:
@validate_arguments
def ud_match(self, word: Word, wordlist: Sequence[Optional[Word]]) -> Optional[str]:
for i in range(len(wordlist) - 2, -2, -2): # backwards through even elements
mo = re.search(fr"^{wordlist[i]}$", word, re.IGNORECASE)
if mo:
if wordlist[i + 1] is None:
return None
pl = DOLLAR_DIGITS.sub(
r"\\1", wordlist[i + 1]
r"\\1", cast(Word, wordlist[i + 1])
) # change $n to \n for expand
return mo.expand(pl)
return None
Expand Down Expand Up @@ -2295,7 +2299,8 @@ def _string_to_substitute(

# 0. PERFORM GENERAL INFLECTIONS IN A STRING

def inflect(self, text: str) -> str:
@validate_arguments
def inflect(self, text: Word) -> str:
"""
Perform inflections in a string.
Expand Down Expand Up @@ -2371,7 +2376,8 @@ def partition_word(self, text: str) -> Tuple[str, str, str]:
else:
return "", "", ""

def plural(self, text: str, count: Optional[Union[str, int]] = None) -> str:
@validate_arguments
def plural(self, text: Word, count: Optional[Union[str, int, Any]] = None) -> str:
"""
Return the plural of text.
Expand All @@ -2394,7 +2400,10 @@ def plural(self, text: str, count: Optional[Union[str, int]] = None) -> str:
)
return f"{pre}{plural}{post}"

def plural_noun(self, text: str, count: Optional[Union[str, int]] = None) -> str:
@validate_arguments
def plural_noun(
self, text: Word, count: Optional[Union[str, int, Any]] = None
) -> str:
"""
Return the plural of text, where text is a noun.
Expand All @@ -2412,7 +2421,10 @@ def plural_noun(self, text: str, count: Optional[Union[str, int]] = None) -> str
plural = self.postprocess(word, self._plnoun(word, count))
return f"{pre}{plural}{post}"

def plural_verb(self, text: str, count: Optional[Union[str, int]] = None) -> str:
@validate_arguments
def plural_verb(
self, text: Word, count: Optional[Union[str, int, Any]] = None
) -> str:
"""
Return the plural of text, where text is a verb.
Expand All @@ -2433,7 +2445,10 @@ def plural_verb(self, text: str, count: Optional[Union[str, int]] = None) -> str
)
return f"{pre}{plural}{post}"

def plural_adj(self, text: str, count: str = None) -> str:
@validate_arguments
def plural_adj(
self, text: Word, count: Optional[Union[str, int, Any]] = None
) -> str:
"""
Return the plural of text, where text is an adjective.
Expand Down Expand Up @@ -2530,10 +2545,11 @@ def compare_adjs(self, word1: Word, word2: Word) -> Union[str, bool]:
"""
return self._plequal(word1, word2, self.plural_adj)

@validate_arguments
def singular_noun(
self,
text: str,
count: Optional[Union[int, str]] = None,
text: Word,
count: Optional[Union[int, str, Any]] = None,
gender: Optional[str] = None,
) -> Union[str, bool]:
"""
Expand Down Expand Up @@ -3465,7 +3481,8 @@ def _sinoun( # noqa: C901

# ADJECTIVES

def a(self, text: str, count: int = 1) -> str:
@validate_arguments
def a(self, text: Word, count: Optional[Union[int, str, Any]] = 1) -> str:
"""
Return the appropriate indefinite article followed by text.
Expand All @@ -3490,7 +3507,9 @@ def a(self, text: str, count: int = 1) -> str:

an = a

def _indef_article(self, word: str, count: int) -> str: # noqa: C901
def _indef_article(
self, word: str, count: Union[int, str, Any]
) -> str: # noqa: C901
mycount = self.get_count(count)

if mycount != 1:
Expand Down Expand Up @@ -3541,7 +3560,8 @@ def _indef_article(self, word: str, count: int) -> str: # noqa: C901

# 2. TRANSLATE ZERO-QUANTIFIED $word TO "no plural($word)"

def no(self, text: str, count: Optional[Union[int, str]] = None) -> str:
@validate_arguments
def no(self, text: Word, count: Optional[Union[int, str]] = None) -> str:
"""
If count is 0, no, zero or nil, return 'no' followed by the plural
of text.
Expand Down Expand Up @@ -3578,7 +3598,8 @@ def no(self, text: str, count: Optional[Union[int, str]] = None) -> str:

# PARTICIPLES

def present_participle(self, word: str) -> str:
@validate_arguments
def present_participle(self, word: Word) -> str:
"""
Return the present participle for word.
Expand All @@ -3596,7 +3617,8 @@ def present_participle(self, word: str) -> str:

# NUMERICAL INFLECTIONS

def ordinal(self, num: Union[int, str]) -> str: # noqa: C901
@validate_arguments
def ordinal(self, num: Union[int, Word]) -> str: # noqa: C901
"""
Return the ordinal of num.
Expand Down Expand Up @@ -3755,16 +3777,17 @@ def enword(self, num: str, group: int) -> str:
num = ONE_DIGIT_WORD.sub(self.unitsub, num, 1)
return num

@validate_arguments(config=dict(arbitrary_types_allowed=True))
def number_to_words( # noqa: C901
self,
num: Union[int, str],
num: Union[Number, Word],
wantlist: bool = False,
group: int = 0,
comma: str = ",",
comma: Union[Falsish, str] = ",",
andword: str = "and",
zero: str = "zero",
one: str = "one",
decimal: str = "point",
decimal: Union[Falsish, str] = "point",
threshold: Optional[int] = None,
) -> Union[str, List[str]]:
"""
Expand Down Expand Up @@ -3906,9 +3929,10 @@ def number_to_words( # noqa: C901

# Join words with commas and a trailing 'and' (when appropriate)...

@validate_arguments
def join(
self,
words: Optional[Sequence[str]],
words: Optional[Sequence[Word]],
sep: Optional[str] = None,
sep_spaced: bool = True,
final_sep: Optional[str] = None,
Expand Down
19 changes: 12 additions & 7 deletions tests/test_pwd.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import unittest

import pytest

from inflect import (
BadChunkingOptionError,
NumOutOfRangeError,
Expand Down Expand Up @@ -253,7 +255,6 @@ def test_partition_word(self):
def test_pl(self):
p = inflect.engine()
for fn, sing, plur in (
(p.plural, "", ""),
(p.plural, "cow", "cows"),
(p.plural, "thought", "thoughts"),
(p.plural, "mouse", "mice"),
Expand All @@ -265,15 +266,12 @@ def test_pl(self):
(p.plural, "carmen", "carmina"),
(p.plural, "quartz", "quartzes"),
(p.plural, "care", "cares"),
(p.plural_noun, "", ""),
(p.plural_noun, "cow", "cows"),
(p.plural_noun, "thought", "thoughts"),
(p.plural_noun, "snooze", "snoozes"),
(p.plural_verb, "", ""),
(p.plural_verb, "runs", "run"),
(p.plural_verb, "thought", "thought"),
(p.plural_verb, "eyes", "eye"),
(p.plural_adj, "", ""),
(p.plural_adj, "a", "some"),
(p.plural_adj, "this", "these"),
(p.plural_adj, "that", "those"),
Expand Down Expand Up @@ -313,6 +311,12 @@ def test_pl(self):
self.assertEqual(p.plural("die"), "dice")
self.assertEqual(p.plural_noun("die"), "dice")

with pytest.raises(Exception):
p.plural("")
p.plural_noun("")
p.plural_verb("")
p.plural_adj("")

def test_sinoun(self):
p = inflect.engine()
for sing, plur in (
Expand Down Expand Up @@ -567,7 +571,6 @@ def test_count(self):
def test__plnoun(self):
p = inflect.engine()
for sing, plur in (
("", ""),
("tuna", "tuna"),
("TUNA", "TUNA"),
("swordfish", "swordfish"),
Expand Down Expand Up @@ -698,7 +701,8 @@ def test_classical_pl(self):

def test__pl_special_verb(self):
p = inflect.engine()
self.assertEqual(p._pl_special_verb(""), False)
with pytest.raises(Exception):
self.assertEqual(p._pl_special_verb(""), False)
self.assertEqual(p._pl_special_verb("am"), "are")
self.assertEqual(p._pl_special_verb("am", 0), "are")
self.assertEqual(p._pl_special_verb("runs", 0), "run")
Expand Down Expand Up @@ -800,7 +804,8 @@ def test_a(self):
self.assertEqual(p.a("cat", 2), "2 cat")

self.assertEqual(p.a, p.an)
self.assertEqual(p.a(""), "")
with pytest.raises(Exception):
p.a("")

def test_no(self):
p = inflect.engine()
Expand Down

0 comments on commit bc8143e

Please sign in to comment.