Skip to content

Commit

Permalink
Merge pull request #199 from abravalheri/issue-195-typeguard
Browse files Browse the repository at this point in the history
Replace `pydantic...validate_call` with `typeguard.typecheck`
  • Loading branch information
jaraco authored Mar 31, 2024
2 parents 8e5842c + c8c27d9 commit 684cd1a
Show file tree
Hide file tree
Showing 9 changed files with 54 additions and 83 deletions.
2 changes: 0 additions & 2 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,6 @@ jobs:
platform: ubuntu-latest
- python: "3.x"
platform: ubuntu-latest
env:
TOX_ENV: pydantic1
runs-on: ${{ matrix.platform }}
continue-on-error: ${{ matrix.python == '3.13' }}
steps:
Expand Down
75 changes: 43 additions & 32 deletions inflect/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
import re
from numbers import Number
from typing import (
TYPE_CHECKING,
Any,
Callable,
Dict,
Expand All @@ -74,12 +75,9 @@
)

from more_itertools import windowed_complete
from pydantic import Field
from typeguard import typechecked
from typing_extensions import Annotated, Literal

from .compat.pydantic import same_method
from .compat.pydantic1 import validate_call


class UnknownClassicalModeError(Exception):
pass
Expand Down Expand Up @@ -2021,10 +2019,25 @@ def __init__(self, orig) -> None:
self.last = self.split_[-1]


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


_STATIC_TYPE_CHECKING = TYPE_CHECKING
# ^-- Workaround for typeguard AST manipulation:
# https://github.com/agronholm/typeguard/issues/353#issuecomment-1556306554

if _STATIC_TYPE_CHECKING: # pragma: no cover
Word = Annotated[str, "String with at least 1 character"]
else:

class _WordMeta(type): # Too dynamic to be supported by mypy...
def __instancecheck__(self, instance: Any) -> bool:
return isinstance(instance, str) and len(instance) >= 1

class Word(metaclass=_WordMeta): # type: ignore[no-redef]
"""String with at least 1 character"""


class engine:
def __init__(self) -> None:
self.classical_dict = def_classical.copy()
Expand All @@ -2046,7 +2059,7 @@ def _number_args(self):
def _number_args(self, val):
self.__number_args = val

@validate_call
@typechecked
def defnoun(self, singular: Optional[Word], plural: Optional[Word]) -> int:
"""
Set the noun plural of singular to plural.
Expand All @@ -2058,7 +2071,7 @@ def defnoun(self, singular: Optional[Word], plural: Optional[Word]) -> int:
self.si_sb_user_defined.extend((plural, singular))
return 1

@validate_call
@typechecked
def defverb(
self,
s1: Optional[Word],
Expand All @@ -2083,7 +2096,7 @@ def defverb(
self.pl_v_user_defined.extend((s1, p1, s2, p2, s3, p3))
return 1

@validate_call
@typechecked
def defadj(self, singular: Optional[Word], plural: Optional[Word]) -> int:
"""
Set the adjective plural of singular to plural.
Expand All @@ -2094,7 +2107,7 @@ def defadj(self, singular: Optional[Word], plural: Optional[Word]) -> int:
self.pl_adj_user_defined.extend((singular, plural))
return 1

@validate_call
@typechecked
def defa(self, pattern: Optional[Word]) -> int:
"""
Define the indefinite article as 'a' for words matching pattern.
Expand All @@ -2104,7 +2117,7 @@ def defa(self, pattern: Optional[Word]) -> int:
self.A_a_user_defined.extend((pattern, "a"))
return 1

@validate_call
@typechecked
def defan(self, pattern: Optional[Word]) -> int:
"""
Define the indefinite article as 'an' for words matching pattern.
Expand All @@ -2131,7 +2144,7 @@ def checkpatplural(self, pattern: Optional[Word]) -> None:
"""
return

@validate_call
@typechecked
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(rf"^{wordlist[i]}$", word, re.IGNORECASE)
Expand Down Expand Up @@ -2271,7 +2284,7 @@ def _string_to_substitute(

# 0. PERFORM GENERAL INFLECTIONS IN A STRING

@validate_call
@typechecked
def inflect(self, text: Word) -> str:
"""
Perform inflections in a string.
Expand Down Expand Up @@ -2348,7 +2361,7 @@ def partition_word(self, text: str) -> Tuple[str, str, str]:
else:
return "", "", ""

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

@validate_call
@typechecked
def plural_noun(
self, text: Word, count: Optional[Union[str, int, Any]] = None
) -> str:
Expand All @@ -2393,7 +2406,7 @@ def plural_noun(
plural = self.postprocess(word, self._plnoun(word, count))
return f"{pre}{plural}{post}"

@validate_call
@typechecked
def plural_verb(
self, text: Word, count: Optional[Union[str, int, Any]] = None
) -> str:
Expand All @@ -2417,7 +2430,7 @@ def plural_verb(
)
return f"{pre}{plural}{post}"

@validate_call
@typechecked
def plural_adj(
self, text: Word, count: Optional[Union[str, int, Any]] = None
) -> str:
Expand All @@ -2438,7 +2451,7 @@ def plural_adj(
plural = self.postprocess(word, self._pl_special_adjective(word, count) or word)
return f"{pre}{plural}{post}"

@validate_call
@typechecked
def compare(self, word1: Word, word2: Word) -> Union[str, bool]:
"""
compare word1 and word2 for equality regardless of plurality
Expand All @@ -2461,15 +2474,13 @@ def compare(self, word1: Word, word2: Word) -> Union[str, bool]:
>>> compare('egg', '')
Traceback (most recent call last):
...
pydantic...ValidationError: ...
...
...at least 1 character...
typeguard.TypeCheckError:...is not an instance of inflect.Word
"""
norms = self.plural_noun, self.plural_verb, self.plural_adj
results = (self._plequal(word1, word2, norm) for norm in norms)
return next(filter(None, results), False)

@validate_call
@typechecked
def compare_nouns(self, word1: Word, word2: Word) -> Union[str, bool]:
"""
compare word1 and word2 for equality regardless of plurality
Expand All @@ -2485,7 +2496,7 @@ def compare_nouns(self, word1: Word, word2: Word) -> Union[str, bool]:
"""
return self._plequal(word1, word2, self.plural_noun)

@validate_call
@typechecked
def compare_verbs(self, word1: Word, word2: Word) -> Union[str, bool]:
"""
compare word1 and word2 for equality regardless of plurality
Expand All @@ -2501,7 +2512,7 @@ def compare_verbs(self, word1: Word, word2: Word) -> Union[str, bool]:
"""
return self._plequal(word1, word2, self.plural_verb)

@validate_call
@typechecked
def compare_adjs(self, word1: Word, word2: Word) -> Union[str, bool]:
"""
compare word1 and word2 for equality regardless of plurality
Expand All @@ -2517,7 +2528,7 @@ def compare_adjs(self, word1: Word, word2: Word) -> Union[str, bool]:
"""
return self._plequal(word1, word2, self.plural_adj)

@validate_call
@typechecked
def singular_noun(
self,
text: Word,
Expand Down Expand Up @@ -2575,12 +2586,12 @@ def _plequal(self, word1: str, word2: str, pl) -> Union[str, bool]: # noqa: C90
return "s:p"
self.classical_dict = classval.copy()

if same_method(pl, self.plural) or same_method(pl, self.plural_noun):
if pl == self.plural or pl == self.plural_noun:
if self._pl_check_plurals_N(word1, word2):
return "p:p"
if self._pl_check_plurals_N(word2, word1):
return "p:p"
if same_method(pl, self.plural) or same_method(pl, self.plural_adj):
if pl == self.plural or pl == self.plural_adj:
if self._pl_check_plurals_adj(word1, word2):
return "p:p"
return False
Expand Down Expand Up @@ -3475,7 +3486,7 @@ def _sinoun( # noqa: C901

# ADJECTIVES

@validate_call
@typechecked
def a(self, text: Word, count: Optional[Union[int, str, Any]] = 1) -> str:
"""
Return the appropriate indefinite article followed by text.
Expand Down Expand Up @@ -3556,7 +3567,7 @@ def _indef_article(self, word: str, count: Union[int, str, Any]) -> str:

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

@validate_call
@typechecked
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
Expand Down Expand Up @@ -3594,7 +3605,7 @@ def no(self, text: Word, count: Optional[Union[int, str]] = None) -> str:

# PARTICIPLES

@validate_call
@typechecked
def present_participle(self, word: Word) -> str:
"""
Return the present participle for word.
Expand All @@ -3613,7 +3624,7 @@ def present_participle(self, word: Word) -> str:

# NUMERICAL INFLECTIONS

@validate_call(config=dict(arbitrary_types_allowed=True))
@typechecked
def ordinal(self, num: Union[Number, Word]) -> str:
"""
Return the ordinal of num.
Expand Down Expand Up @@ -3772,7 +3783,7 @@ def enword(self, num: str, group: int) -> str:
num = ONE_DIGIT_WORD.sub(self.unitsub, num, 1)
return num

@validate_call(config=dict(arbitrary_types_allowed=True)) # noqa: C901
@typechecked
def number_to_words( # noqa: C901
self,
num: Union[Number, Word],
Expand Down Expand Up @@ -3924,7 +3935,7 @@ def number_to_words( # noqa: C901

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

@validate_call
@typechecked
def join(
self,
words: Optional[Sequence[Word]],
Expand Down
Empty file removed inflect/compat/__init__.py
Empty file.
27 changes: 0 additions & 27 deletions inflect/compat/pydantic.py

This file was deleted.

8 changes: 0 additions & 8 deletions inflect/compat/pydantic1.py

This file was deleted.

1 change: 1 addition & 0 deletions newsfragments/195.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Replace pydantic with typeguard
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ include_package_data = true
python_requires = >=3.8
install_requires =
more_itertools
pydantic >= 1.9.1
typeguard >= 4.0.1
typing_extensions
keywords = plural inflect participle

Expand Down
18 changes: 9 additions & 9 deletions tests/test_pwd.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import pytest
from typeguard import TypeCheckError

import inflect
from inflect import (
Expand All @@ -8,7 +9,6 @@
NumOutOfRangeError,
UnknownClassicalModeError,
)
from inflect.compat.pydantic import same_method

missing = object()

Expand Down Expand Up @@ -290,13 +290,13 @@ def test_pl(self):
assert p.plural("die") == "dice"
assert p.plural_noun("die") == "dice"

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

def test_sinoun(self):
Expand Down Expand Up @@ -696,7 +696,7 @@ def test_classical_pl(self):

def test__pl_special_verb(self):
p = inflect.engine()
with pytest.raises(ValueError):
with pytest.raises(TypeCheckError):
p._pl_special_verb("")
assert p._pl_special_verb("am") == "are"
assert p._pl_special_verb("am", 0) == "are"
Expand Down Expand Up @@ -819,13 +819,13 @@ def test_a_alt(self):
assert p.a("cat", 1) == "a cat"
assert p.a("cat", 2) == "2 cat"

with pytest.raises(ValueError):
with pytest.raises(TypeCheckError):
p.a("")

def test_a_and_an_same_method(self):
assert same_method(inflect.engine.a, inflect.engine.an)
assert inflect.engine.a == inflect.engine.an
p = inflect.engine()
assert same_method(p.a, p.an)
assert p.a == p.an

def test_no(self):
p = inflect.engine()
Expand Down
4 changes: 0 additions & 4 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,6 @@ usedevelop = True
extras =
testing

[testenv:pydantic1]
deps =
pydantic < 2

[testenv:diffcov]
description = run tests and check that diff from main is covered
deps =
Expand Down

0 comments on commit 684cd1a

Please sign in to comment.