Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace pydantic...validate_call with typeguard.typecheck #199

Merged
merged 10 commits into from
Mar 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading