New Upstream Release - python-inflect

Ready changes

Summary

Merged new upstream version: 7.0.0 (was: 6.0.4).

Resulting package

Built on 2023-08-14T17:21 (took 5m31s)

The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:

apt install -t fresh-releases python3-inflect

Lintian Result

Diff

diff --git a/.flake8 b/.flake8
deleted file mode 100644
index 48b2e24..0000000
--- a/.flake8
+++ /dev/null
@@ -1,9 +0,0 @@
-[flake8]
-max-line-length = 88
-
-# jaraco/skeleton#34
-max-complexity = 10
-
-extend-ignore =
-	# Black creates whitespace before colon
-	E203
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 9629a26..2c1d27e 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -2,6 +2,9 @@ name: tests
 
 on: [push, pull_request]
 
+permissions:
+  contents: read
+
 env:
   # Environment variables to support color support (jaraco/skeleton#66):
   # Request colored output from CLI tools supporting it. Different tools
@@ -39,25 +42,24 @@ jobs:
     strategy:
       matrix:
         python:
-        - "3.7"
+        - "3.8"
         - "3.11"
         - "3.12"
-        # Workaround for actions/setup-python#508
-        dev:
-        - -dev
         platform:
         - ubuntu-latest
         - macos-latest
         - windows-latest
         include:
-        - python: "3.8"
-          platform: ubuntu-latest
         - python: "3.9"
           platform: ubuntu-latest
         - python: "3.10"
           platform: ubuntu-latest
         - python: pypy3.9
           platform: ubuntu-latest
+        - python: "3.x"
+          platform: ubuntu-latest
+          env:
+            TOX_ENV: pydantic1
     runs-on: ${{ matrix.platform }}
     continue-on-error: ${{ matrix.python == '3.12' }}
     steps:
@@ -65,7 +67,8 @@ jobs:
       - name: Setup Python
         uses: actions/setup-python@v4
         with:
-          python-version: ${{ matrix.python }}${{ matrix.dev }}
+          python-version: ${{ matrix.python }}
+          allow-prereleases: true
       - name: Install tox
         run: |
           python -m pip install tox
@@ -80,8 +83,6 @@ jobs:
       - uses: actions/checkout@v3
       - name: Setup Python
         uses: actions/setup-python@v4
-        with:
-          python-version: ${{ matrix.python }}${{ matrix.dev }}
       - name: Install tox
         run: |
           python -m pip install tox
@@ -104,6 +105,8 @@ jobs:
         jobs: ${{ toJSON(needs) }}
 
   release:
+    permissions:
+      contents: write
     needs:
     - check
     if: github.event_name == 'push' && contains(github.ref, 'refs/tags/')
diff --git a/.readthedocs.yaml b/.readthedocs.yaml
index 6bef349..053c728 100644
--- a/.readthedocs.yaml
+++ b/.readthedocs.yaml
@@ -5,9 +5,8 @@ python:
     extra_requirements:
       - docs
 
-# workaround for readthedocs/readthedocs.org#9623
+# required boilerplate readthedocs/readthedocs.org#10401
 build:
-  # workaround for readthedocs/readthedocs.org#9635
   os: ubuntu-22.04
   tools:
     python: "3"
diff --git a/LICENSE b/LICENSE
index 353924b..1bb5a44 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,5 +1,3 @@
-Copyright Jason R. Coombs
-
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to
 deal in the Software without restriction, including without limitation the
diff --git a/CHANGES.rst b/NEWS.rst
similarity index 89%
rename from CHANGES.rst
rename to NEWS.rst
index 2178306..a57b538 100644
--- a/CHANGES.rst
+++ b/NEWS.rst
@@ -1,3 +1,56 @@
+v7.0.0
+======
+
+Features
+--------
+
+- Refine type hint for ``singular_noun`` to indicate a literal return type for ``False``. (#186)
+
+
+Deprecations and Removals
+-------------------------
+
+- Removed methods renamed in 0.2.0.
+
+
+v6.2.0
+======
+
+Features
+--------
+
+- Project now supports Pydantic 2 while retaining support for Pydantic 1. (#187)
+
+
+Bugfixes
+--------
+
+- Added validation of user-defined words and amended the type declarations to match, allowing for null values but not empty strings. (#187)
+
+
+v6.1.1
+======
+
+Bugfixes
+--------
+
+- ``ordinal`` now handles float types correctly without first coercing them to strings. (#178)
+
+
+v6.1.0
+======
+
+Features
+--------
+
+- Require Python 3.8 or later.
+
+
+v6.0.5
+======
+
+* #187: Pin to Pydantic 1 to avoid breaking in Pydantic 2.
+
 v6.0.4
 ======
 
diff --git a/PKG-INFO b/PKG-INFO
index 5295526..60c0732 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: inflect
-Version: 6.0.4
+Version: 7.0.0
 Summary: Correctly generate plurals, singular nouns, ordinals, indefinite articles; convert numbers to words
 Home-page: https://github.com/jaraco/inflect
 Author: Paul Dyson
@@ -16,7 +16,7 @@ Classifier: Natural Language :: English
 Classifier: Operating System :: OS Independent
 Classifier: Topic :: Software Development :: Libraries :: Python Modules
 Classifier: Topic :: Text Processing :: Linguistic
-Requires-Python: >=3.7
+Requires-Python: >=3.8
 Provides-Extra: testing
 Provides-Extra: docs
 License-File: LICENSE
@@ -30,6 +30,10 @@ License-File: LICENSE
    :target: https://github.com/jaraco/inflect/actions?query=workflow%3A%22tests%22
    :alt: tests
 
+.. image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json
+    :target: https://github.com/astral-sh/ruff
+    :alt: Ruff
+
 .. image:: https://img.shields.io/badge/code%20style-black-000000.svg
    :target: https://github.com/psf/black
    :alt: Code style: Black
diff --git a/README.rst b/README.rst
index d61f59b..42a0fb3 100644
--- a/README.rst
+++ b/README.rst
@@ -7,6 +7,10 @@
    :target: https://github.com/jaraco/inflect/actions?query=workflow%3A%22tests%22
    :alt: tests
 
+.. image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json
+    :target: https://github.com/astral-sh/ruff
+    :alt: Ruff
+
 .. image:: https://img.shields.io/badge/code%20style-black-000000.svg
    :target: https://github.com/psf/black
    :alt: Code style: Black
diff --git a/debian/changelog b/debian/changelog
index d74b94f..3b30fd1 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+python-inflect (7.0.0-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Mon, 14 Aug 2023 17:16:47 -0000
+
 python-inflect (6.0.4-1) unstable; urgency=medium
 
   [ Debian Janitor ]
diff --git a/docs/conf.py b/docs/conf.py
index 4e5eae3..c973dd9 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -9,7 +9,7 @@ html_theme = "furo"
 # Link dates and other references in the changelog
 extensions += ['rst.linker']
 link_files = {
-    '../CHANGES.rst': dict(
+    '../NEWS.rst': dict(
         using=dict(GH='https://github.com'),
         replace=[
             dict(
diff --git a/docs/history.rst b/docs/history.rst
index 8e21750..5bdc232 100644
--- a/docs/history.rst
+++ b/docs/history.rst
@@ -5,4 +5,4 @@
 History
 *******
 
-.. include:: ../CHANGES (links).rst
+.. include:: ../NEWS (links).rst
diff --git a/inflect.egg-info/PKG-INFO b/inflect.egg-info/PKG-INFO
index 5295526..60c0732 100644
--- a/inflect.egg-info/PKG-INFO
+++ b/inflect.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: inflect
-Version: 6.0.4
+Version: 7.0.0
 Summary: Correctly generate plurals, singular nouns, ordinals, indefinite articles; convert numbers to words
 Home-page: https://github.com/jaraco/inflect
 Author: Paul Dyson
@@ -16,7 +16,7 @@ Classifier: Natural Language :: English
 Classifier: Operating System :: OS Independent
 Classifier: Topic :: Software Development :: Libraries :: Python Modules
 Classifier: Topic :: Text Processing :: Linguistic
-Requires-Python: >=3.7
+Requires-Python: >=3.8
 Provides-Extra: testing
 Provides-Extra: docs
 License-File: LICENSE
@@ -30,6 +30,10 @@ License-File: LICENSE
    :target: https://github.com/jaraco/inflect/actions?query=workflow%3A%22tests%22
    :alt: tests
 
+.. image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json
+    :target: https://github.com/astral-sh/ruff
+    :alt: Ruff
+
 .. image:: https://img.shields.io/badge/code%20style-black-000000.svg
    :target: https://github.com/psf/black
    :alt: Code style: Black
diff --git a/inflect.egg-info/SOURCES.txt b/inflect.egg-info/SOURCES.txt
index 0202261..8e1bb49 100644
--- a/inflect.egg-info/SOURCES.txt
+++ b/inflect.egg-info/SOURCES.txt
@@ -1,16 +1,16 @@
 .coveragerc
 .editorconfig
-.flake8
 .gitignore
 .pre-commit-config.yaml
 .readthedocs.yaml
-CHANGES.rst
 LICENSE
+NEWS.rst
 README.rst
 mypy.ini
 pyproject.toml
 pytest.ini
 setup.cfg
+towncrier.toml
 tox.ini
 .github/FUNDING.yml
 .github/dependabot.yml
@@ -25,6 +25,9 @@ inflect.egg-info/SOURCES.txt
 inflect.egg-info/dependency_links.txt
 inflect.egg-info/requires.txt
 inflect.egg-info/top_level.txt
+inflect/compat/__init__.py
+inflect/compat/pydantic.py
+inflect/compat/pydantic1.py
 tests/inflections.txt
 tests/test_an.py
 tests/test_classical_all.py
diff --git a/inflect.egg-info/requires.txt b/inflect.egg-info/requires.txt
index 9308940..b4c715f 100644
--- a/inflect.egg-info/requires.txt
+++ b/inflect.egg-info/requires.txt
@@ -1,4 +1,5 @@
 pydantic>=1.9.1
+typing_extensions
 
 [docs]
 sphinx>=3.5
@@ -11,14 +12,11 @@ jaraco.tidelift>=1.4
 [testing]
 pytest>=6
 pytest-checkdocs>=2.4
-flake8<5
 pytest-cov
-pytest-enabler>=1.3
+pytest-enabler>=2.2
+pytest-ruff
 pygments
 
 [testing:platform_python_implementation != "PyPy"]
 pytest-black>=0.3.7
 pytest-mypy>=0.9.1
-
-[testing:python_version < "3.12"]
-pytest-flake8
diff --git a/inflect/__init__.py b/inflect/__init__.py
index 1c8bd34..b638c6b 100644
--- a/inflect/__init__.py
+++ b/inflect/__init__.py
@@ -3,8 +3,6 @@ inflect: english language inflection
  - correctly generate plurals, ordinals, indefinite articles
  - convert numbers to words
 
-Copyright (C) 2010 Paul Dyson
-
 Based upon the Perl module
 `Lingua::EN::Inflect <https://metacpan.org/pod/Lingua::EN::Inflect>`_.
 
@@ -70,11 +68,16 @@ from typing import (
     cast,
     Any,
 )
+from typing_extensions import Literal
 from numbers import Number
 
 
-from pydantic import Field, validate_arguments
-from pydantic.typing import Annotated
+from pydantic import Field
+from typing_extensions import Annotated
+
+
+from .compat.pydantic1 import validate_call
+from .compat.pydantic import same_method
 
 
 class UnknownClassicalModeError(Exception):
@@ -105,14 +108,6 @@ class BadGenderError(Exception):
     pass
 
 
-STDOUT_ON = False
-
-
-def print3(txt: str) -> None:
-    if STDOUT_ON:
-        print(txt)
-
-
 def enclose(s: str) -> str:
     return f"(?:{s})"
 
@@ -1727,66 +1722,44 @@ plverb_irregular_pres = {
     "is": "are",
     "was": "were",
     "were": "were",
-    "was": "were",
-    "have": "have",
     "have": "have",
     "has": "have",
     "do": "do",
-    "do": "do",
     "does": "do",
 }
 
 plverb_ambiguous_pres = {
-    "act": "act",
     "act": "act",
     "acts": "act",
     "blame": "blame",
-    "blame": "blame",
     "blames": "blame",
     "can": "can",
-    "can": "can",
-    "can": "can",
-    "must": "must",
     "must": "must",
-    "must": "must",
-    "fly": "fly",
     "fly": "fly",
     "flies": "fly",
     "copy": "copy",
-    "copy": "copy",
     "copies": "copy",
     "drink": "drink",
-    "drink": "drink",
     "drinks": "drink",
     "fight": "fight",
-    "fight": "fight",
     "fights": "fight",
     "fire": "fire",
-    "fire": "fire",
     "fires": "fire",
     "like": "like",
-    "like": "like",
     "likes": "like",
     "look": "look",
-    "look": "look",
     "looks": "look",
     "make": "make",
-    "make": "make",
     "makes": "make",
     "reach": "reach",
-    "reach": "reach",
     "reaches": "reach",
     "run": "run",
-    "run": "run",
     "runs": "run",
     "sink": "sink",
-    "sink": "sink",
     "sinks": "sink",
     "sleep": "sleep",
-    "sleep": "sleep",
     "sleeps": "sleep",
     "view": "view",
-    "view": "view",
     "views": "view",
 }
 
@@ -2056,11 +2029,11 @@ class engine:
         self.classical_dict = def_classical.copy()
         self.persistent_count: Optional[int] = None
         self.mill_count = 0
-        self.pl_sb_user_defined: List[str] = []
-        self.pl_v_user_defined: List[str] = []
-        self.pl_adj_user_defined: List[str] = []
-        self.si_sb_user_defined: List[str] = []
-        self.A_a_user_defined: List[str] = []
+        self.pl_sb_user_defined: List[Optional[Word]] = []
+        self.pl_v_user_defined: List[Optional[Word]] = []
+        self.pl_adj_user_defined: List[Optional[Word]] = []
+        self.si_sb_user_defined: List[Optional[Word]] = []
+        self.A_a_user_defined: List[Optional[Word]] = []
         self.thegender = "neuter"
         self.__number_args: Optional[Dict[str, str]] = None
 
@@ -2072,28 +2045,8 @@ class engine:
     def _number_args(self, val):
         self.__number_args = val
 
-    deprecated_methods = dict(
-        pl="plural",
-        plnoun="plural_noun",
-        plverb="plural_verb",
-        pladj="plural_adj",
-        sinoun="single_noun",
-        prespart="present_participle",
-        numwords="number_to_words",
-        plequal="compare",
-        plnounequal="compare_nouns",
-        plverbequal="compare_verbs",
-        pladjequal="compare_adjs",
-        wordlist="join",
-    )
-
-    def __getattr__(self, meth):
-        if meth in self.deprecated_methods:
-            print3(f"{meth}() deprecated, use {self.deprecated_methods[meth]}()")
-            raise DeprecationWarning
-        raise AttributeError
-
-    def defnoun(self, singular: str, plural: str) -> int:
+    @validate_call
+    def defnoun(self, singular: Optional[Word], plural: Optional[Word]) -> int:
         """
         Set the noun plural of singular to plural.
 
@@ -2104,7 +2057,16 @@ class engine:
         self.si_sb_user_defined.extend((plural, singular))
         return 1
 
-    def defverb(self, s1: str, p1: str, s2: str, p2: str, s3: str, p3: str) -> int:
+    @validate_call
+    def defverb(
+        self,
+        s1: Optional[Word],
+        p1: Optional[Word],
+        s2: Optional[Word],
+        p2: Optional[Word],
+        s3: Optional[Word],
+        p3: Optional[Word],
+    ) -> int:
         """
         Set the verb plurals for s1, s2 and s3 to p1, p2 and p3 respectively.
 
@@ -2120,7 +2082,8 @@ class engine:
         self.pl_v_user_defined.extend((s1, p1, s2, p2, s3, p3))
         return 1
 
-    def defadj(self, singular: str, plural: str) -> int:
+    @validate_call
+    def defadj(self, singular: Optional[Word], plural: Optional[Word]) -> int:
         """
         Set the adjective plural of singular to plural.
 
@@ -2130,7 +2093,8 @@ class engine:
         self.pl_adj_user_defined.extend((singular, plural))
         return 1
 
-    def defa(self, pattern: str) -> int:
+    @validate_call
+    def defa(self, pattern: Optional[Word]) -> int:
         """
         Define the indefinite article as 'a' for words matching pattern.
 
@@ -2139,7 +2103,8 @@ class engine:
         self.A_a_user_defined.extend((pattern, "a"))
         return 1
 
-    def defan(self, pattern: str) -> int:
+    @validate_call
+    def defan(self, pattern: Optional[Word]) -> int:
         """
         Define the indefinite article as 'an' for words matching pattern.
 
@@ -2148,7 +2113,7 @@ class engine:
         self.A_a_user_defined.extend((pattern, "an"))
         return 1
 
-    def checkpat(self, pattern: Optional[str]) -> None:
+    def checkpat(self, pattern: Optional[Word]) -> None:
         """
         check for errors in a regex pattern
         """
@@ -2157,16 +2122,15 @@ class engine:
         try:
             re.match(pattern, "")
         except re.error:
-            print3(f"\nBad user-defined singular pattern:\n\t{pattern}\n")
-            raise BadUserDefinedPatternError
+            raise BadUserDefinedPatternError(pattern)
 
-    def checkpatplural(self, pattern: str) -> None:
+    def checkpatplural(self, pattern: Optional[Word]) -> None:
         """
         check for errors in a regex replace pattern
         """
         return
 
-    @validate_arguments
+    @validate_call
     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)
@@ -2306,7 +2270,7 @@ class engine:
 
     # 0. PERFORM GENERAL INFLECTIONS IN A STRING
 
-    @validate_arguments
+    @validate_call
     def inflect(self, text: Word) -> str:
         """
         Perform inflections in a string.
@@ -2383,7 +2347,7 @@ class engine:
         else:
             return "", "", ""
 
-    @validate_arguments
+    @validate_call
     def plural(self, text: Word, count: Optional[Union[str, int, Any]] = None) -> str:
         """
         Return the plural of text.
@@ -2407,7 +2371,7 @@ class engine:
         )
         return f"{pre}{plural}{post}"
 
-    @validate_arguments
+    @validate_call
     def plural_noun(
         self, text: Word, count: Optional[Union[str, int, Any]] = None
     ) -> str:
@@ -2428,7 +2392,7 @@ class engine:
         plural = self.postprocess(word, self._plnoun(word, count))
         return f"{pre}{plural}{post}"
 
-    @validate_arguments
+    @validate_call
     def plural_verb(
         self, text: Word, count: Optional[Union[str, int, Any]] = None
     ) -> str:
@@ -2452,7 +2416,7 @@ class engine:
         )
         return f"{pre}{plural}{post}"
 
-    @validate_arguments
+    @validate_call
     def plural_adj(
         self, text: Word, count: Optional[Union[str, int, Any]] = None
     ) -> str:
@@ -2473,7 +2437,7 @@ class engine:
         plural = self.postprocess(word, self._pl_special_adjective(word, count) or word)
         return f"{pre}{plural}{post}"
 
-    @validate_arguments
+    @validate_call
     def compare(self, word1: Word, word2: Word) -> Union[str, bool]:
         """
         compare word1 and word2 for equality regardless of plurality
@@ -2496,15 +2460,15 @@ class engine:
         >>> compare('egg', '')
         Traceback (most recent call last):
         ...
-        pydantic.error_wrappers.ValidationError: 1 validation error for Compare
-        word2
-          ensure this value has at least 1 characters...
+        pydantic...ValidationError: ...
+        ...
+          ...at least 1 characters...
         """
         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_arguments
+    @validate_call
     def compare_nouns(self, word1: Word, word2: Word) -> Union[str, bool]:
         """
         compare word1 and word2 for equality regardless of plurality
@@ -2520,7 +2484,7 @@ class engine:
         """
         return self._plequal(word1, word2, self.plural_noun)
 
-    @validate_arguments
+    @validate_call
     def compare_verbs(self, word1: Word, word2: Word) -> Union[str, bool]:
         """
         compare word1 and word2 for equality regardless of plurality
@@ -2536,7 +2500,7 @@ class engine:
         """
         return self._plequal(word1, word2, self.plural_verb)
 
-    @validate_arguments
+    @validate_call
     def compare_adjs(self, word1: Word, word2: Word) -> Union[str, bool]:
         """
         compare word1 and word2 for equality regardless of plurality
@@ -2552,13 +2516,13 @@ class engine:
         """
         return self._plequal(word1, word2, self.plural_adj)
 
-    @validate_arguments
+    @validate_call
     def singular_noun(
         self,
         text: Word,
         count: Optional[Union[int, str, Any]] = None,
         gender: Optional[str] = None,
-    ) -> Union[str, bool]:
+    ) -> Union[str, Literal[False]]:
         """
         Return the singular of text, where text is a plural noun.
 
@@ -2610,12 +2574,12 @@ class engine:
             return "s:p"
         self.classical_dict = classval.copy()
 
-        if pl == self.plural or pl == self.plural_noun:
+        if same_method(pl, self.plural) or same_method(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 pl == self.plural or pl == self.plural_adj:
+        if same_method(pl, self.plural) or same_method(pl, self.plural_adj):
             if self._pl_check_plurals_adj(word1, word2):
                 return "p:p"
         return False
@@ -3265,11 +3229,11 @@ class engine:
 
         if words.last in si_sb_irregular_caps:
             llen = len(words.last)
-            return "{}{}".format(word[:-llen], si_sb_irregular_caps[words.last])
+            return f"{word[:-llen]}{si_sb_irregular_caps[words.last]}"
 
         if words.last.lower() in si_sb_irregular:
             llen = len(words.last.lower())
-            return "{}{}".format(word[:-llen], si_sb_irregular[words.last.lower()])
+            return f"{word[:-llen]}{si_sb_irregular[words.last.lower()]}"
 
         dash_split = words.lowered.split("-")
         if (" ".join(dash_split[-2:])).lower() in si_sb_irregular_compound:
@@ -3486,7 +3450,7 @@ class engine:
 
     # ADJECTIVES
 
-    @validate_arguments
+    @validate_call
     def a(self, text: Word, count: Optional[Union[int, str, Any]] = 1) -> str:
         """
         Return the appropriate indefinite article followed by text.
@@ -3567,7 +3531,7 @@ class engine:
 
     # 2. TRANSLATE ZERO-QUANTIFIED $word TO "no plural($word)"
 
-    @validate_arguments
+    @validate_call
     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
@@ -3605,7 +3569,7 @@ class engine:
 
     # PARTICIPLES
 
-    @validate_arguments
+    @validate_call
     def present_participle(self, word: Word) -> str:
         """
         Return the present participle for word.
@@ -3624,31 +3588,31 @@ class engine:
 
     # NUMERICAL INFLECTIONS
 
-    @validate_arguments
-    def ordinal(self, num: Word) -> str:
+    @validate_call(config=dict(arbitrary_types_allowed=True))
+    def ordinal(self, num: Union[Number, Word]) -> str:
         """
         Return the ordinal of num.
 
-        num can be an integer or text
-
-        e.g. ordinal(1) returns '1st'
-        ordinal('one') returns 'first'
-
+        >>> ordinal = engine().ordinal
+        >>> ordinal(1)
+        '1st'
+        >>> ordinal('one')
+        'first'
         """
         if DIGIT.match(str(num)):
-            if isinstance(num, (int, float)):
+            if isinstance(num, (float, int)) and int(num) == num:
                 n = int(num)
             else:
                 if "." in str(num):
                     try:
                         # numbers after decimal,
                         # so only need last one for ordinal
-                        n = int(num[-1])
+                        n = int(str(num)[-1])
 
                     except ValueError:  # ends with '.', so need to use whole string
-                        n = int(num[:-1])
+                        n = int(str(num)[:-1])
                 else:
-                    n = int(num)
+                    n = int(num)  # type: ignore
             try:
                 post = nth[n % 100]
             except KeyError:
@@ -3657,7 +3621,7 @@ class engine:
         else:
             # Mad props to Damian Conway (?) whose ordinal()
             # algorithm is type-bendy enough to foil MyPy
-            str_num: str = num  # type:	ignore[assignment]
+            str_num: str = num  # type: ignore[assignment]
             mo = ordinal_suff.search(str_num)
             if mo:
                 post = ordinal[mo.group(1)]
@@ -3668,7 +3632,6 @@ class engine:
 
     def millfn(self, ind: int = 0) -> str:
         if ind > len(mill) - 1:
-            print3("number out of range")
             raise NumOutOfRangeError
         return mill[ind]
 
@@ -3784,7 +3747,7 @@ class engine:
             num = ONE_DIGIT_WORD.sub(self.unitsub, num, 1)
         return num
 
-    @validate_arguments(config=dict(arbitrary_types_allowed=True))  # noqa: C901
+    @validate_call(config=dict(arbitrary_types_allowed=True))  # noqa: C901
     def number_to_words(  # noqa: C901
         self,
         num: Union[Number, Word],
@@ -3936,7 +3899,7 @@ class engine:
 
     # Join words with commas and a trailing 'and' (when appropriate)...
 
-    @validate_arguments
+    @validate_call
     def join(
         self,
         words: Optional[Sequence[Word]],
diff --git a/inflect/compat/__init__.py b/inflect/compat/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/inflect/compat/pydantic.py b/inflect/compat/pydantic.py
new file mode 100644
index 0000000..d777564
--- /dev/null
+++ b/inflect/compat/pydantic.py
@@ -0,0 +1,19 @@
+class ValidateCallWrapperWrapper:
+    def __init__(self, wrapped):
+        self.orig = wrapped
+
+    def __eq__(self, other):
+        return self.raw_function == other.raw_function
+
+    @property
+    def raw_function(self):
+        return getattr(self.orig, 'raw_function') or self.orig
+
+
+def same_method(m1, m2) -> bool:
+    """
+    Return whether m1 and m2 are the same method.
+
+    Workaround for pydantic/pydantic#6390.
+    """
+    return ValidateCallWrapperWrapper(m1) == ValidateCallWrapperWrapper(m2)
diff --git a/inflect/compat/pydantic1.py b/inflect/compat/pydantic1.py
new file mode 100644
index 0000000..8262fdc
--- /dev/null
+++ b/inflect/compat/pydantic1.py
@@ -0,0 +1,8 @@
+try:
+    from pydantic import validate_call  # type: ignore
+except ImportError:
+    # Pydantic 1
+    from pydantic import validate_arguments as validate_call  # type: ignore
+
+
+__all__ = ['validate_call']
diff --git a/pyproject.toml b/pyproject.toml
index 60de242..dce944d 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -6,15 +6,3 @@ build-backend = "setuptools.build_meta"
 skip-string-normalization = true
 
 [tool.setuptools_scm]
-
-[tool.pytest-enabler.black]
-addopts = "--black"
-
-[tool.pytest-enabler.mypy]
-addopts = "--mypy"
-
-[tool.pytest-enabler.flake8]
-addopts = "--flake8"
-
-[tool.pytest-enabler.cov]
-addopts = "--cov"
diff --git a/pytest.ini b/pytest.ini
index 99a2519..d9a15ed 100644
--- a/pytest.ini
+++ b/pytest.ini
@@ -7,19 +7,11 @@ filterwarnings=
 	# Ensure ResourceWarnings are emitted
 	default::ResourceWarning
 
-	# Suppress deprecation warning in flake8
-	ignore:SelectableGroups dict interface is deprecated::flake8
-
 	# shopkeep/pytest-black#55
 	ignore:<class 'pytest_black.BlackItem'> is not using a cooperative constructor:pytest.PytestDeprecationWarning
 	ignore:The \(fspath. py.path.local\) argument to BlackItem is deprecated.:pytest.PytestDeprecationWarning
 	ignore:BlackItem is an Item subclass and should not be a collector:pytest.PytestWarning
 
-	# tholo/pytest-flake8#83
-	ignore:<class 'pytest_flake8.Flake8Item'> is not using a cooperative constructor:pytest.PytestDeprecationWarning
-	ignore:The \(fspath. py.path.local\) argument to Flake8Item is deprecated.:pytest.PytestDeprecationWarning
-	ignore:Flake8Item is an Item subclass and should not be a collector:pytest.PytestWarning
-
 	# shopkeep/pytest-black#67
 	ignore:'encoding' argument not specified::pytest_black
 
@@ -29,4 +21,7 @@ filterwarnings=
 	# python/cpython#100750
 	ignore:'encoding' argument not specified::platform
 
+	# pypa/build#615
+	ignore:'encoding' argument not specified::build.env
+
 	## end upstream
diff --git a/setup.cfg b/setup.cfg
index 2720d6f..0b94e3a 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -21,9 +21,10 @@ classifiers =
 [options]
 packages = find_namespace:
 include_package_data = true
-python_requires = >=3.7
+python_requires = >=3.8
 install_requires = 
 	pydantic >= 1.9.1
+	typing_extensions
 keywords = plural inflect participle
 
 [options.packages.find]
@@ -37,15 +38,13 @@ exclude =
 testing = 
 	pytest >= 6
 	pytest-checkdocs >= 2.4
-	pytest-flake8; \
-	python_version < "3.12"
-	flake8 < 5
 	pytest-black >= 0.3.7; \
 	python_implementation != "PyPy"
 	pytest-cov
 	pytest-mypy >= 0.9.1; \
 	python_implementation != "PyPy"
-	pytest-enabler >= 1.3
+	pytest-enabler >= 2.2
+	pytest-ruff
 	
 	pygments
 docs = 
diff --git a/tests/test_classical_all.py b/tests/test_classical_all.py
index d30afd1..348e0ff 100644
--- a/tests/test_classical_all.py
+++ b/tests/test_classical_all.py
@@ -1,106 +1,49 @@
-import unittest
-
 import inflect
 
 
-class test(unittest.TestCase):
+class Test:
     def test_classical(self):
         p = inflect.engine()
 
         # DEFAULT...
 
-        self.assertEqual(
-            p.plural_noun("error", 0), "errors", msg="classical 'zero' not active"
-        )
-        self.assertEqual(
-            p.plural_noun("wildebeest"),
-            "wildebeests",
-            msg="classical 'herd' not active",
-        )
-        self.assertEqual(
-            p.plural_noun("Sally"), "Sallys", msg="classical 'names' active"
-        )
-        self.assertEqual(
-            p.plural_noun("brother"), "brothers", msg="classical others not active"
-        )
-        self.assertEqual(
-            p.plural_noun("person"), "people", msg="classical 'persons' not active"
-        )
-        self.assertEqual(
-            p.plural_noun("formula"), "formulas", msg="classical 'ancient' not active"
-        )
+        assert p.plural_noun("error", 0) == "errors", "classical 'zero' not active"
+        assert (
+            p.plural_noun("wildebeest") == "wildebeests"
+        ), "classical 'herd' not active"
+        assert p.plural_noun("Sally") == "Sallys", "classical 'names' active"
+        assert p.plural_noun("brother") == "brothers", "classical others not active"
+        assert p.plural_noun("person") == "people", "classical 'persons' not active"
+        assert p.plural_noun("formula") == "formulas", "classical 'ancient' not active"
 
         # CLASSICAL PLURALS ACTIVATED...
 
         p.classical(all=True)
-        self.assertEqual(
-            p.plural_noun("error", 0), "error", msg="classical 'zero' active"
-        )
-        self.assertEqual(
-            p.plural_noun("wildebeest"), "wildebeest", msg="classical 'herd' active"
-        )
-        self.assertEqual(
-            p.plural_noun("Sally"), "Sallys", msg="classical 'names' active"
-        )
-        self.assertEqual(
-            p.plural_noun("brother"), "brethren", msg="classical others active"
-        )
-        self.assertEqual(
-            p.plural_noun("person"), "persons", msg="classical 'persons' active"
-        )
-        self.assertEqual(
-            p.plural_noun("formula"), "formulae", msg="classical 'ancient' active"
-        )
+        assert p.plural_noun("error", 0) == "error", "classical 'zero' active"
+        assert p.plural_noun("wildebeest") == "wildebeest", "classical 'herd' active"
+        assert p.plural_noun("Sally") == "Sallys", "classical 'names' active"
+        assert p.plural_noun("brother") == "brethren", "classical others active"
+        assert p.plural_noun("person") == "persons", "classical 'persons' active"
+        assert p.plural_noun("formula") == "formulae", "classical 'ancient' active"
 
         # CLASSICAL PLURALS DEACTIVATED...
 
         p.classical(all=False)
-        self.assertEqual(
-            p.plural_noun("error", 0), "errors", msg="classical 'zero' not active"
-        )
-        self.assertEqual(
-            p.plural_noun("wildebeest"),
-            "wildebeests",
-            msg="classical 'herd' not active",
-        )
-        self.assertEqual(
-            p.plural_noun("Sally"), "Sallies", msg="classical 'names' not active"
-        )
-        self.assertEqual(
-            p.plural_noun("brother"), "brothers", msg="classical others not active"
-        )
-        self.assertEqual(
-            p.plural_noun("person"), "people", msg="classical 'persons' not active"
-        )
-        self.assertEqual(
-            p.plural_noun("formula"), "formulas", msg="classical 'ancient' not active"
-        )
+        assert p.plural_noun("error", 0) == "errors", "classical 'zero' not active"
+        assert (
+            p.plural_noun("wildebeest") == "wildebeests"
+        ), "classical 'herd' not active"
+        assert p.plural_noun("Sally") == "Sallies", "classical 'names' not active"
+        assert p.plural_noun("brother") == "brothers", "classical others not active"
+        assert p.plural_noun("person") == "people", "classical 'persons' not active"
+        assert p.plural_noun("formula") == "formulas", "classical 'ancient' not active"
 
         # CLASSICAL PLURALS REREREACTIVATED...
 
         p.classical()
-        self.assertEqual(
-            p.plural_noun("error", 0), "error", msg="classical 'zero' active"
-        )
-        self.assertEqual(
-            p.plural_noun("wildebeest"), "wildebeest", msg="classical 'herd' active"
-        )
-        self.assertEqual(
-            p.plural_noun("Sally"), "Sallys", msg="classical 'names' active"
-        )
-        self.assertEqual(
-            p.plural_noun("brother"), "brethren", msg="classical others active"
-        )
-        self.assertEqual(
-            p.plural_noun("person"), "persons", msg="classical 'persons' active"
-        )
-        self.assertEqual(
-            p.plural_noun("formula"), "formulae", msg="classical 'ancient' active"
-        )
-
-
-if __name__ == "__main__":
-    try:
-        unittest.main()
-    except SystemExit:
-        pass
+        assert p.plural_noun("error", 0) == "error", "classical 'zero' active"
+        assert p.plural_noun("wildebeest") == "wildebeest", "classical 'herd' active"
+        assert p.plural_noun("Sally") == "Sallys", "classical 'names' active"
+        assert p.plural_noun("brother") == "brethren", "classical others active"
+        assert p.plural_noun("person") == "persons", "classical 'persons' active"
+        assert p.plural_noun("formula") == "formulae", "classical 'ancient' active"
diff --git a/tests/test_inflections.py b/tests/test_inflections.py
index bfbded8..7a49416 100644
--- a/tests/test_inflections.py
+++ b/tests/test_inflections.py
@@ -1,5 +1,4 @@
 import os
-import io
 
 import pytest
 
@@ -82,7 +81,7 @@ def check_all(p, is_nv, singular, mod_PL_val, class_PL_val, mod_plural, class_pl
     assert is_eq(p, singular, class_plural) in ("s:p", "p:s", "eq")
     assert is_eq(p, class_plural, singular) in ("p:s", "s:p", "eq")
     assert singular != ""
-    expected = mod_PL_val if class_PL_val else "%s|%s" % (mod_PL_val, class_PL_val)
+    expected = mod_PL_val if class_PL_val else "{}|{}".format(mod_PL_val, class_PL_val)
     assert mod_PL_val == expected
 
     if is_nv != "_V":
@@ -122,7 +121,7 @@ def test_def():
 def test_ordinal():
     p = inflect.engine()
     assert p.ordinal(0) == "0th"
-    assert p.ordinal(1) == "1st"
+    assert p.ordinal(1) == p.ordinal("1") == "1st"
     assert p.ordinal(2) == "2nd"
     assert p.ordinal(3) == "3rd"
     assert p.ordinal(4) == "4th"
@@ -152,7 +151,7 @@ def test_ordinal():
     assert p.ordinal(103) == "103rd"
     assert p.ordinal(104) == "104th"
 
-    assert p.ordinal(1.1) == "1.1st"
+    assert p.ordinal(1.1) == p.ordinal("1.1") == "1.1st"
     assert p.ordinal(1.2) == "1.2nd"
     assert p.ordinal(5.502) == "5.502nd"
 
@@ -265,5 +264,5 @@ def test_NameError_in_strings():
 
 def get_data():
     filename = os.path.join(os.path.dirname(__file__), "inflections.txt")
-    with io.open(filename, encoding='utf-8') as strm:
+    with open(filename, encoding='utf-8') as strm:
         return list(map(str.strip, strm))
diff --git a/tests/test_pwd.py b/tests/test_pwd.py
index a8b841e..acacf00 100644
--- a/tests/test_pwd.py
+++ b/tests/test_pwd.py
@@ -1,7 +1,3 @@
-#!/usr/bin/python
-
-import unittest
-
 import pytest
 
 from inflect import (
@@ -12,114 +8,102 @@ from inflect import (
     UnknownClassicalModeError,
 )
 import inflect
+from inflect.compat.pydantic import same_method
 
 
-class test(unittest.TestCase):
-    def TODO(
-        self,
-        ans,
-        answer_wanted,
-        answer_gives_now="default_that_will_never_occur__can't_use_None"
-        "_as_that_is_a_possible_valid_value",
-    ):
-        """
-        make this test for future testing
-
-        so can easily rename these to assertEqual when code ready
-        """
-        if ans == answer_wanted:
-            print("test unexpectedly passed!: {} == {}".format(ans, answer_wanted))
-        if answer_gives_now != (
-            "default_that_will_never_occur__can't_use_None"
-            "_as_that_is_a_possible_valid_value"
-        ):
-            self.assertEqual(ans, answer_gives_now)
+missing = object()
+
 
+class Test:
     def test_enclose(self):
         # def enclose
-        self.assertEqual(inflect.enclose("test"), "(?:test)")
+        assert inflect.enclose("test") == "(?:test)"
 
     def test_joinstem(self):
         # def joinstem
-        self.assertEqual(
-            inflect.joinstem(-2, ["ephemeris", "iris", ".*itis"]), "(?:ephemer|ir|.*it)"
+        assert (
+            inflect.joinstem(-2, ["ephemeris", "iris", ".*itis"])
+            == "(?:ephemer|ir|.*it)"
         )
 
     def test_classical(self):
         # classical dicts
-        self.assertEqual(
-            set(inflect.def_classical.keys()), set(inflect.all_classical.keys())
-        )
-        self.assertEqual(
-            set(inflect.def_classical.keys()), set(inflect.no_classical.keys())
-        )
+        assert set(inflect.def_classical.keys()) == set(inflect.all_classical.keys())
+        assert set(inflect.def_classical.keys()) == set(inflect.no_classical.keys())
 
         # def classical
         p = inflect.engine()
-        self.assertEqual(p.classical_dict, inflect.def_classical)
+        assert p.classical_dict == inflect.def_classical
 
         p.classical()
-        self.assertEqual(p.classical_dict, inflect.all_classical)
-
-        self.assertRaises(TypeError, p.classical, 0)
-        self.assertRaises(TypeError, p.classical, 1)
-        self.assertRaises(TypeError, p.classical, "names")
-        self.assertRaises(TypeError, p.classical, "names", "zero")
-        self.assertRaises(TypeError, p.classical, "all")
+        assert p.classical_dict == inflect.all_classical
+
+        with pytest.raises(TypeError):
+            p.classical(0)
+        with pytest.raises(TypeError):
+            p.classical(1)
+        with pytest.raises(TypeError):
+            p.classical("names")
+        with pytest.raises(TypeError):
+            p.classical("names", "zero")
+        with pytest.raises(TypeError):
+            p.classical("all")
 
         p.classical(all=False)
-        self.assertEqual(p.classical_dict, inflect.no_classical)
+        assert p.classical_dict == inflect.no_classical
 
         p.classical(names=True, zero=True)
         mydict = inflect.def_classical.copy()
         mydict.update(dict(names=1, zero=1))
-        self.assertEqual(p.classical_dict, mydict)
+        assert p.classical_dict == mydict
 
         p.classical(all=True)
-        self.assertEqual(p.classical_dict, inflect.all_classical)
+        assert p.classical_dict == inflect.all_classical
 
         p.classical(all=False)
         p.classical(names=True, zero=True)
         mydict = inflect.def_classical.copy()
         mydict.update(dict(names=True, zero=True))
-        self.assertEqual(p.classical_dict, mydict)
+        assert p.classical_dict == mydict
 
         p.classical(all=False)
         p.classical(names=True, zero=False)
         mydict = inflect.def_classical.copy()
         mydict.update(dict(names=True, zero=False))
-        self.assertEqual(p.classical_dict, mydict)
+        assert p.classical_dict == mydict
 
-        self.assertRaises(UnknownClassicalModeError, p.classical, bogus=True)
+        with pytest.raises(UnknownClassicalModeError):
+            p.classical(bogus=True)
 
     def test_num(self):
         # def num
         p = inflect.engine()
-        self.assertTrue(p.persistent_count is None)
+        assert p.persistent_count is None
 
         p.num()
-        self.assertTrue(p.persistent_count is None)
+        assert p.persistent_count is None
 
         ret = p.num(3)
-        self.assertEqual(p.persistent_count, 3)
-        self.assertEqual(ret, "3")
+        assert p.persistent_count == 3
+        assert ret == "3"
 
         p.num()
         ret = p.num("3")
-        self.assertEqual(p.persistent_count, 3)
-        self.assertEqual(ret, "3")
+        assert p.persistent_count == 3
+        assert ret == "3"
 
         p.num()
         ret = p.num(count=3, show=1)
-        self.assertEqual(p.persistent_count, 3)
-        self.assertEqual(ret, "3")
+        assert p.persistent_count == 3
+        assert ret == "3"
 
         p.num()
         ret = p.num(count=3, show=0)
-        self.assertEqual(p.persistent_count, 3)
-        self.assertEqual(ret, "")
+        assert p.persistent_count == 3
+        assert ret == ""
 
-        self.assertRaises(BadNumValueError, p.num, "text")
+        with pytest.raises(BadNumValueError):
+            p.num("text")
 
     def test_inflect(self):
         p = inflect.engine()
@@ -131,9 +115,7 @@ class test(unittest.TestCase):
             ("   num(1)   ", "   1   "),
             ("num(3) num(1)", "3 1"),
         ):
-            self.assertEqual(
-                p.inflect(txt), ans, msg='p.inflect("{}") != "{}"'.format(txt, ans)
-            )
+            assert p.inflect(txt) == ans, f'p.inflect("{txt}") != "{ans}"'
 
         for txt, ans in (
             ("plural('rock')", "rocks"),
@@ -151,73 +133,75 @@ class test(unittest.TestCase):
             ),
             ("a('cat',0) a('cat',1) a('cat',2) a('cat', 2)", "0 cat a cat 2 cat 2 cat"),
         ):
-            self.assertEqual(
-                p.inflect(txt), ans, msg='p.inflect("{}") != "{}"'.format(txt, ans)
-            )
+            assert p.inflect(txt) == ans, f'p.inflect("{txt}") != "{ans}"'
 
     def test_user_input_fns(self):
         p = inflect.engine()
 
-        self.assertEqual(p.pl_sb_user_defined, [])
+        assert p.pl_sb_user_defined == []
         p.defnoun("VAX", "VAXen")
-        self.assertEqual(p.plural("VAX"), "VAXEN")
-        self.assertEqual(p.pl_sb_user_defined, ["VAX", "VAXen"])
+        assert p.plural("VAX") == "VAXEN"
+        assert p.pl_sb_user_defined == ["VAX", "VAXen"]
 
-        self.assertTrue(p.ud_match("word", p.pl_sb_user_defined) is None)
-        self.assertEqual(p.ud_match("VAX", p.pl_sb_user_defined), "VAXen")
-        self.assertTrue(p.ud_match("VVAX", p.pl_sb_user_defined) is None)
+        assert p.ud_match("word", p.pl_sb_user_defined) is None
+        assert p.ud_match("VAX", p.pl_sb_user_defined) == "VAXen"
+        assert p.ud_match("VVAX", p.pl_sb_user_defined) is None
 
         p.defnoun("cow", "cows|kine")
-        self.assertEqual(p.plural("cow"), "cows")
+        assert p.plural("cow") == "cows"
         p.classical()
-        self.assertEqual(p.plural("cow"), "kine")
+        assert p.plural("cow") == "kine"
 
-        self.assertEqual(p.ud_match("cow", p.pl_sb_user_defined), "cows|kine")
+        assert p.ud_match("cow", p.pl_sb_user_defined) == "cows|kine"
 
         p.defnoun("(.+i)o", r"$1i")
-        self.assertEqual(p.plural("studio"), "studii")
-        self.assertEqual(p.ud_match("studio", p.pl_sb_user_defined), "studii")
+        assert p.plural("studio") == "studii"
+        assert p.ud_match("studio", p.pl_sb_user_defined) == "studii"
 
         p.defnoun("aviatrix", "aviatrices")
-        self.assertEqual(p.plural("aviatrix"), "aviatrices")
-        self.assertEqual(p.ud_match("aviatrix", p.pl_sb_user_defined), "aviatrices")
+        assert p.plural("aviatrix") == "aviatrices"
+        assert p.ud_match("aviatrix", p.pl_sb_user_defined) == "aviatrices"
         p.defnoun("aviatrix", "aviatrixes")
-        self.assertEqual(p.plural("aviatrix"), "aviatrixes")
-        self.assertEqual(p.ud_match("aviatrix", p.pl_sb_user_defined), "aviatrixes")
+        assert p.plural("aviatrix") == "aviatrixes"
+        assert p.ud_match("aviatrix", p.pl_sb_user_defined) == "aviatrixes"
         p.defnoun("aviatrix", None)
-        self.assertEqual(p.plural("aviatrix"), "aviatrices")
-        self.assertEqual(p.ud_match("aviatrix", p.pl_sb_user_defined), None)
+        assert p.plural("aviatrix") == "aviatrices"
+        assert p.ud_match("aviatrix", p.pl_sb_user_defined) is None
 
         p.defnoun("(cat)", r"$1s")
-        self.assertEqual(p.plural("cat"), "cats")
+        assert p.plural("cat") == "cats"
 
-        inflect.STDOUT_ON = False
-        self.assertRaises(inflect.BadUserDefinedPatternError, p.defnoun, "(??", None)
-        inflect.STDOUT_ON = True
+        with pytest.raises(inflect.BadUserDefinedPatternError):
+            p.defnoun("(??", None)
 
-        p.defnoun(None, "")  # check None doesn't crash it
-
-        # defverb
-        p.defverb("will", "shall", "will", "will", "will", "will")
-        self.assertEqual(p.ud_match("will", p.pl_v_user_defined), "will")
-        self.assertEqual(p.plural("will"), "will")
-        # TODO: will -> shall. Tests below fail
-        self.TODO(p.compare("will", "shall"), "s:p")
-        self.TODO(p.compare_verbs("will", "shall"), "s:p")
+        p.defnoun(None, "any")  # check None doesn't crash it
 
         # defadj
         p.defadj("hir", "their")
-        self.assertEqual(p.plural("hir"), "their")
-        self.assertEqual(p.ud_match("hir", p.pl_adj_user_defined), "their")
+        assert p.plural("hir") == "their"
+        assert p.ud_match("hir", p.pl_adj_user_defined) == "their"
 
         # defa defan
         p.defa("h")
-        self.assertEqual(p.a("h"), "a h")
-        self.assertEqual(p.ud_match("h", p.A_a_user_defined), "a")
+        assert p.a("h") == "a h"
+        assert p.ud_match("h", p.A_a_user_defined) == "a"
 
         p.defan("horrendous.*")
-        self.assertEqual(p.a("horrendously"), "an horrendously")
-        self.assertEqual(p.ud_match("horrendously", p.A_a_user_defined), "an")
+        assert p.a("horrendously") == "an horrendously"
+        assert p.ud_match("horrendously", p.A_a_user_defined) == "an"
+
+    def test_user_input_defverb(self):
+        p = inflect.engine()
+        p.defverb("will", "shall", "will", "will", "will", "will")
+        assert p.ud_match("will", p.pl_v_user_defined) == "will"
+        assert p.plural("will") == "will"
+
+    @pytest.mark.xfail(reason="todo")
+    def test_user_input_defverb_compare(self):
+        p = inflect.engine()
+        p.defverb("will", "shall", "will", "will", "will", "will")
+        assert p.compare("will", "shall") == "s:p"
+        assert p.compare_verbs("will", "shall") == "s:p"
 
     def test_postprocess(self):
         p = inflect.engine()
@@ -230,10 +214,10 @@ class test(unittest.TestCase):
             ("Entry", "entries", "Entries"),
             ("can of Coke", "cans of coke", "cans of Coke"),
         ):
-            self.assertEqual(p.postprocess(orig, infl), txt)
+            assert p.postprocess(orig, infl) == txt
 
         p.classical()
-        self.assertEqual(p.postprocess("cow", "cows|kine"), "kine")
+        assert p.postprocess("cow", "cows|kine") == "kine"
 
     def test_partition_word(self):
         p = inflect.engine()
@@ -250,7 +234,7 @@ class test(unittest.TestCase):
             # ('  '),(' ', ' ', '')),
             # ('   '),('  ', ' ', '')),
         ):
-            self.assertEqual(p.partition_word(txt), part)
+            assert p.partition_word(txt) == part
 
     def test_pl(self):
         p = inflect.engine()
@@ -279,12 +263,8 @@ class test(unittest.TestCase):
             (p.plural_adj, "cat's", "cats'"),
             (p.plural_adj, "child's", "children's"),
         ):
-            self.assertEqual(
-                fn(sing),
-                plur,
-                msg='{}("{}") == "{}" != "{}"'.format(
-                    fn.__name__, sing, fn(sing), plur
-                ),
+            assert fn(sing) == plur, '{}("{}") == "{}" != "{}"'.format(
+                fn.__name__, sing, fn(sing), plur
             )
 
         for sing, num, plur in (
@@ -299,17 +279,17 @@ class test(unittest.TestCase):
             ("runs", 1, "runs"),
             ("am", 0, "are"),
         ):
-            self.assertEqual(p.plural(sing, num), plur)
+            assert p.plural(sing, num) == plur
 
         p.classical(zero=True)
-        self.assertEqual(p.plural("cow", 0), "cow")
-        self.assertEqual(p.plural("cow", "zero"), "cow")
-        self.assertEqual(p.plural("runs", 0), "runs")
-        self.assertEqual(p.plural("am", 0), "am")
-        self.assertEqual(p.plural_verb("runs", 1), "runs")
+        assert p.plural("cow", 0) == "cow"
+        assert p.plural("cow", "zero") == "cow"
+        assert p.plural("runs", 0) == "runs"
+        assert p.plural("am", 0) == "am"
+        assert p.plural_verb("runs", 1) == "runs"
 
-        self.assertEqual(p.plural("die"), "dice")
-        self.assertEqual(p.plural_noun("die"), "dice")
+        assert p.plural("die") == "dice"
+        assert p.plural_noun("die") == "dice"
 
         with pytest.raises(Exception):
             p.plural("")
@@ -324,30 +304,30 @@ class test(unittest.TestCase):
             ("die", "dice"),
             ("goose", "geese"),
         ):
-            self.assertEqual(p.singular_noun(plur), sing)
-            self.assertEqual(p.inflect("singular_noun('%s')" % plur), sing)
+            assert p.singular_noun(plur) == sing
+            assert p.inflect("singular_noun('%s')" % plur) == sing
 
-        self.assertEqual(p.singular_noun("cats", count=2), "cats")
-        self.assertEqual(p.singular_noun("open valves", count=2), "open valves")
+        assert p.singular_noun("cats", count=2) == "cats"
+        assert p.singular_noun("open valves", count=2) == "open valves"
 
-        self.assertEqual(p.singular_noun("zombies"), "zombie")
+        assert p.singular_noun("zombies") == "zombie"
 
-        self.assertEqual(p.singular_noun("shoes"), "shoe")
-        self.assertEqual(p.singular_noun("dancing shoes"), "dancing shoe")
+        assert p.singular_noun("shoes") == "shoe"
+        assert p.singular_noun("dancing shoes") == "dancing shoe"
 
-        self.assertEqual(p.singular_noun("Matisses"), "Matisse")
-        self.assertEqual(p.singular_noun("bouillabaisses"), "bouillabaisse")
+        assert p.singular_noun("Matisses") == "Matisse"
+        assert p.singular_noun("bouillabaisses") == "bouillabaisse"
 
-        self.assertEqual(p.singular_noun("quartzes"), "quartz")
+        assert p.singular_noun("quartzes") == "quartz"
 
-        self.assertEqual(p.singular_noun("Nietzsches"), "Nietzsche")
-        self.assertEqual(p.singular_noun("aches"), "ache")
+        assert p.singular_noun("Nietzsches") == "Nietzsche"
+        assert p.singular_noun("aches") == "ache"
 
-        self.assertEqual(p.singular_noun("Clives"), "Clive")
-        self.assertEqual(p.singular_noun("weaves"), "weave")
+        assert p.singular_noun("Clives") == "Clive"
+        assert p.singular_noun("weaves") == "weave"
 
-        self.assertEqual(p.singular_noun("status"), False)
-        self.assertEqual(p.singular_noun("hiatus"), False)
+        assert p.singular_noun("status") is False
+        assert p.singular_noun("hiatus") is False
 
     def test_gender(self):
         p = inflect.engine()
@@ -359,14 +339,10 @@ class test(unittest.TestCase):
             ("to her", "to them"),
             ("to herself", "to themselves"),
         ):
-            self.assertEqual(
-                p.singular_noun(plur),
-                sing,
-                "singular_noun({}) == {} != {}".format(
-                    plur, p.singular_noun(plur), sing
-                ),
-            )
-            self.assertEqual(p.inflect("singular_noun('%s')" % plur), sing)
+            assert (
+                p.singular_noun(plur) == sing
+            ), f"singular_noun({plur}) == {p.singular_noun(plur)} != {sing}"
+            assert p.inflect("singular_noun('%s')" % plur) == sing
 
         p.gender("masculine")
         for sing, plur in (
@@ -376,14 +352,10 @@ class test(unittest.TestCase):
             ("to him", "to them"),
             ("to himself", "to themselves"),
         ):
-            self.assertEqual(
-                p.singular_noun(plur),
-                sing,
-                "singular_noun({}) == {} != {}".format(
-                    plur, p.singular_noun(plur), sing
-                ),
-            )
-            self.assertEqual(p.inflect("singular_noun('%s')" % plur), sing)
+            assert (
+                p.singular_noun(plur) == sing
+            ), f"singular_noun({plur}) == {p.singular_noun(plur)} != {sing}"
+            assert p.inflect("singular_noun('%s')" % plur) == sing
 
         p.gender("gender-neutral")
         for sing, plur in (
@@ -393,14 +365,10 @@ class test(unittest.TestCase):
             ("to them", "to them"),
             ("to themself", "to themselves"),
         ):
-            self.assertEqual(
-                p.singular_noun(plur),
-                sing,
-                "singular_noun({}) == {} != {}".format(
-                    plur, p.singular_noun(plur), sing
-                ),
-            )
-            self.assertEqual(p.inflect("singular_noun('%s')" % plur), sing)
+            assert (
+                p.singular_noun(plur) == sing
+            ), f"singular_noun({plur}) == {p.singular_noun(plur)} != {sing}"
+            assert p.inflect("singular_noun('%s')" % plur) == sing
 
         p.gender("neuter")
         for sing, plur in (
@@ -410,16 +378,13 @@ class test(unittest.TestCase):
             ("to it", "to them"),
             ("to itself", "to themselves"),
         ):
-            self.assertEqual(
-                p.singular_noun(plur),
-                sing,
-                "singular_noun({}) == {} != {}".format(
-                    plur, p.singular_noun(plur), sing
-                ),
-            )
-            self.assertEqual(p.inflect("singular_noun('%s')" % plur), sing)
+            assert (
+                p.singular_noun(plur) == sing
+            ), f"singular_noun({plur}) == {p.singular_noun(plur)} != {sing}"
+            assert p.inflect("singular_noun('%s')" % plur) == sing
 
-        self.assertRaises(BadGenderError, p.gender, "male")
+        with pytest.raises(BadGenderError):
+            p.gender("male")
 
         for sing, plur, gen in (
             ("it", "they", "neuter"),
@@ -429,87 +394,111 @@ class test(unittest.TestCase):
             ("she or he", "they", "feminine or masculine"),
             ("he or she", "they", "masculine or feminine"),
         ):
-            self.assertEqual(p.singular_noun(plur, gender=gen), sing)
+            assert p.singular_noun(plur, gender=gen) == sing
 
-        with self.assertRaises(BadGenderError):
+        with pytest.raises(BadGenderError):
             p.singular_noun("cats", gender="unknown gender")
 
-    def test_plequal(self):
+    @pytest.mark.parametrize(
+        'sing,plur,res',
+        (
+            ("index", "index", "eq"),
+            ("index", "indexes", "s:p"),
+            ("index", "indices", "s:p"),
+            ("indexes", "index", "p:s"),
+            ("indices", "index", "p:s"),
+            ("indices", "indexes", "p:p"),
+            ("indexes", "indices", "p:p"),
+            ("indices", "indices", "eq"),
+            ("inverted index", "inverted indices", "s:p"),
+            ("inverted indices", "inverted index", "p:s"),
+            ("inverted indexes", "inverted indices", "p:p"),
+            ("opuses", "opera", "p:p"),
+            ("opera", "opuses", "p:p"),
+            ("brothers", "brethren", "p:p"),
+            ("cats", "cats", "eq"),
+            ("base", "basis", False),
+            ("syrinx", "syringe", False),
+            ("she", "he", False),
+            ("opus", "operas", False),
+            ("taxi", "taxes", False),
+            ("time", "Times", False),
+            ("time".lower(), "Times".lower(), "s:p"),
+            ("courts martial", "court martial", "p:s"),
+            ("my", "my", "eq"),
+            ("my", "our", "s:p"),
+            ("our", "our", "eq"),
+            pytest.param(
+                "dresses's", "dresses'", "p:p", marks=pytest.mark.xfail(reason="todo")
+            ),
+            pytest.param(
+                "dress's", "dress'", "s:s", marks=pytest.mark.xfail(reason='todo')
+            ),
+            pytest.param(
+                "Jess's", "Jess'", "s:s", marks=pytest.mark.xfail(reason='todo')
+            ),
+        ),
+    )
+    def test_compare_simple(self, sing, plur, res):
+        assert inflect.engine().compare(sing, plur) == res
+
+    @pytest.mark.parametrize(
+        'sing,plur,res',
+        (
+            ("index", "index", "eq"),
+            ("index", "indexes", "s:p"),
+            ("index", "indices", "s:p"),
+            ("indexes", "index", "p:s"),
+            ("indices", "index", "p:s"),
+            ("indices", "indexes", "p:p"),
+            ("indexes", "indices", "p:p"),
+            ("indices", "indices", "eq"),
+            ("inverted index", "inverted indices", "s:p"),
+            ("inverted indices", "inverted index", "p:s"),
+            ("inverted indexes", "inverted indices", "p:p"),
+        ),
+    )
+    def test_compare_nouns(self, sing, plur, res):
+        assert inflect.engine().compare_nouns(sing, plur) == res
+
+    @pytest.mark.parametrize(
+        'sing,plur,res',
+        (
+            ("runs", "runs", "eq"),
+            ("runs", "run", "s:p"),
+            ("run", "run", "eq"),
+        ),
+    )
+    def test_compare_verbs(self, sing, plur, res):
+        assert inflect.engine().compare_verbs(sing, plur) == res
+
+    @pytest.mark.parametrize(
+        'sing,plur,res',
+        (
+            ("my", "my", "eq"),
+            ("my", "our", "s:p"),
+            ("our", "our", "eq"),
+            pytest.param(
+                "dresses's", "dresses'", "p:p", marks=pytest.mark.xfail(reason="todo")
+            ),
+            pytest.param(
+                "dress's", "dress'", "s:s", marks=pytest.mark.xfail(reason='todo')
+            ),
+            pytest.param(
+                "Jess's", "Jess'", "s:s", marks=pytest.mark.xfail(reason='todo')
+            ),
+        ),
+    )
+    def test_compare_adjectives(self, sing, plur, res):
+        assert inflect.engine().compare_adjs(sing, plur) == res
+
+    @pytest.mark.xfail()
+    def test_compare_your_our(self):
+        # multiple adjective plurals not (yet) supported
         p = inflect.engine()
-        for fn, sing, plur, res in (
-            (p.compare, "index", "index", "eq"),
-            (p.compare, "index", "indexes", "s:p"),
-            (p.compare, "index", "indices", "s:p"),
-            (p.compare, "indexes", "index", "p:s"),
-            (p.compare, "indices", "index", "p:s"),
-            (p.compare, "indices", "indexes", "p:p"),
-            (p.compare, "indexes", "indices", "p:p"),
-            (p.compare, "indices", "indices", "eq"),
-            (p.compare, "inverted index", "inverted indices", "s:p"),
-            (p.compare, "inverted indices", "inverted index", "p:s"),
-            (p.compare, "inverted indexes", "inverted indices", "p:p"),
-            (p.compare, "opuses", "opera", "p:p"),
-            (p.compare, "opera", "opuses", "p:p"),
-            (p.compare, "brothers", "brethren", "p:p"),
-            (p.compare, "cats", "cats", "eq"),
-            (p.compare, "base", "basis", False),
-            (p.compare, "syrinx", "syringe", False),
-            (p.compare, "she", "he", False),
-            (p.compare, "opus", "operas", False),
-            (p.compare, "taxi", "taxes", False),
-            (p.compare, "time", "Times", False),
-            (p.compare, "time".lower(), "Times".lower(), "s:p"),
-            (p.compare, "courts martial", "court martial", "p:s"),
-            (p.compare, "my", "my", "eq"),
-            (p.compare, "my", "our", "s:p"),
-            (p.compare, "our", "our", "eq"),
-            (p.compare_nouns, "index", "index", "eq"),
-            (p.compare_nouns, "index", "indexes", "s:p"),
-            (p.compare_nouns, "index", "indices", "s:p"),
-            (p.compare_nouns, "indexes", "index", "p:s"),
-            (p.compare_nouns, "indices", "index", "p:s"),
-            (p.compare_nouns, "indices", "indexes", "p:p"),
-            (p.compare_nouns, "indexes", "indices", "p:p"),
-            (p.compare_nouns, "indices", "indices", "eq"),
-            (p.compare_nouns, "inverted index", "inverted indices", "s:p"),
-            (p.compare_nouns, "inverted indices", "inverted index", "p:s"),
-            (p.compare_nouns, "inverted indexes", "inverted indices", "p:p"),
-            (p.compare_verbs, "runs", "runs", "eq"),
-            (p.compare_verbs, "runs", "run", "s:p"),
-            (p.compare_verbs, "run", "run", "eq"),
-            (p.compare_adjs, "my", "my", "eq"),
-            (p.compare_adjs, "my", "our", "s:p"),
-            (p.compare_adjs, "our", "our", "eq"),
-        ):
-            self.assertEqual(fn(sing, plur), res)
-
-        for fn, sing, plur, res, badres in (
-            (
-                p.compare,
-                "dresses's",
-                "dresses'",
-                "p:p",
-                "p:s",
-            ),  # TODO: should return p:p
-            (
-                p.compare_adjs,
-                "dresses's",
-                "dresses'",
-                "p:p",
-                False,
-            ),  # TODO: should return p:p
-            # TODO: future: support different singulars one day.
-            (p.compare, "dress's", "dress'", "s:s", "p:s"),
-            (p.compare_adjs, "dress's", "dress'", "s:s", False),
-            (p.compare, "Jess's", "Jess'", "s:s", "p:s"),
-            (p.compare_adjs, "Jess's", "Jess'", "s:s", False),
-        ):
-            self.TODO(fn(sing, plur), res, badres)
-
-        # TODO: pass upstream. multiple adjective plurals not supported
-        self.assertEqual(p.compare("your", "our"), False)
+        assert p.compare("your", "our") is False
         p.defadj("my", "our|your")  # what's ours is yours
-        self.TODO(p.compare("your", "our"), "p:p")
+        assert p.compare("your", "our") == "p:p"
 
     def test__pl_reg_plurals(self):
         p = inflect.engine()
@@ -518,26 +507,26 @@ class test(unittest.TestCase):
             ("indexes|robots", "dummy|ind", "exes", "ices", False),
             ("beaus|beaux", ".*eau", "s", "x", True),
         ):
-            self.assertEqual(p._pl_reg_plurals(pair, stems, end1, end2), ans)
+            assert p._pl_reg_plurals(pair, stems, end1, end2) == ans
 
     def test__pl_check_plurals_N(self):
         p = inflect.engine()
-        self.assertEqual(p._pl_check_plurals_N("index", "indices"), False)
-        self.assertEqual(p._pl_check_plurals_N("indexes", "indices"), True)
-        self.assertEqual(p._pl_check_plurals_N("indices", "indexes"), True)
-        self.assertEqual(p._pl_check_plurals_N("stigmata", "stigmas"), True)
-        self.assertEqual(p._pl_check_plurals_N("phalanxes", "phalanges"), True)
+        assert p._pl_check_plurals_N("index", "indices") is False
+        assert p._pl_check_plurals_N("indexes", "indices") is True
+        assert p._pl_check_plurals_N("indices", "indexes") is True
+        assert p._pl_check_plurals_N("stigmata", "stigmas") is True
+        assert p._pl_check_plurals_N("phalanxes", "phalanges") is True
 
     def test__pl_check_plurals_adj(self):
         p = inflect.engine()
-        self.assertEqual(p._pl_check_plurals_adj("indexes's", "indices's"), True)
-        self.assertEqual(p._pl_check_plurals_adj("indices's", "indexes's"), True)
-        self.assertEqual(p._pl_check_plurals_adj("indexes'", "indices's"), True)
-        self.assertEqual(p._pl_check_plurals_adj("indexes's", "indices'"), True)
-        self.assertEqual(p._pl_check_plurals_adj("indexes's", "indexes's"), False)
-        self.assertEqual(p._pl_check_plurals_adj("dogmas's", "dogmata's"), True)
-        self.assertEqual(p._pl_check_plurals_adj("dogmas'", "dogmata'"), True)
-        self.assertEqual(p._pl_check_plurals_adj("indexes'", "indices'"), True)
+        assert p._pl_check_plurals_adj("indexes's", "indices's") is True
+        assert p._pl_check_plurals_adj("indices's", "indexes's") is True
+        assert p._pl_check_plurals_adj("indexes'", "indices's") is True
+        assert p._pl_check_plurals_adj("indexes's", "indices'") is True
+        assert p._pl_check_plurals_adj("indexes's", "indexes's") is False
+        assert p._pl_check_plurals_adj("dogmas's", "dogmata's") is True
+        assert p._pl_check_plurals_adj("dogmas'", "dogmata'") is True
+        assert p._pl_check_plurals_adj("indexes'", "indices'") is True
 
     def test_count(self):
         p = inflect.engine()
@@ -562,11 +551,11 @@ class test(unittest.TestCase):
             ("that", 1),
             ("dummy", 2),
         ):
-            self.assertEqual(p.get_count(txt), num)
+            assert p.get_count(txt) == num
 
-        self.assertEqual(p.get_count(), "")
+        assert p.get_count() == ""
         p.num(3)
-        self.assertEqual(p.get_count(), 2)
+        assert p.get_count() == 2
 
     def test__plnoun(self):
         p = inflect.engine()
@@ -621,15 +610,11 @@ class test(unittest.TestCase):
             ("zoo", "zoos"),
             ("tomato", "tomatoes"),
         ):
-            self.assertEqual(
-                p._plnoun(sing),
-                plur,
-                msg='p._plnoun("{}") == {} != "{}"'.format(sing, p._plnoun(sing), plur),
+            assert p._plnoun(sing) == plur, 'p._plnoun("{}") == {} != "{}"'.format(
+                sing, p._plnoun(sing), plur
             )
 
-            self.assertEqual(
-                p._sinoun(plur), sing, msg='p._sinoun("{}") != "{}"'.format(plur, sing)
-            )
+            assert p._sinoun(plur) == sing, f'p._sinoun("{plur}") != "{sing}"'
 
         # words where forming singular is ambiguous or not attempted
         for sing, plur in (
@@ -638,34 +623,24 @@ class test(unittest.TestCase):
             ("basis", "bases"),
             ("Jess", "Jesses"),
         ):
-            self.assertEqual(
-                p._plnoun(sing), plur, msg='p._plnoun("{}") != "{}"'.format(sing, plur)
-            )
-
-        for sing, plur in (
-            # TODO: does not keep case
-            ("about ME", "about US"),
-            # TODO: does not keep case
-            ("YOU", "YOU"),
-        ):
-            self.TODO(p._plnoun(sing), plur)
+            assert p._plnoun(sing) == plur, f'p._plnoun("{sing}") != "{plur}"'
 
         p.num(1)
-        self.assertEqual(p._plnoun("cat"), "cat")
+        assert p._plnoun("cat") == "cat"
         p.num(3)
 
         p.classical(herd=True)
-        self.assertEqual(p._plnoun("swine"), "swine")
+        assert p._plnoun("swine") == "swine"
         p.classical(herd=False)
-        self.assertEqual(p._plnoun("swine"), "swines")
+        assert p._plnoun("swine") == "swines"
         p.classical(persons=True)
-        self.assertEqual(p._plnoun("chairperson"), "chairpersons")
+        assert p._plnoun("chairperson") == "chairpersons"
         p.classical(persons=False)
-        self.assertEqual(p._plnoun("chairperson"), "chairpeople")
+        assert p._plnoun("chairperson") == "chairpeople"
         p.classical(ancient=True)
-        self.assertEqual(p._plnoun("formula"), "formulae")
+        assert p._plnoun("formula") == "formulae"
         p.classical(ancient=False)
-        self.assertEqual(p._plnoun("formula"), "formulas")
+        assert p._plnoun("formula") == "formulas"
 
         p.classical()
         for sing, plur in (
@@ -687,75 +662,109 @@ class test(unittest.TestCase):
             ("goy", "goyim"),
             ("afrit", "afriti"),
         ):
-            self.assertEqual(p._plnoun(sing), plur)
+            assert p._plnoun(sing) == plur
 
         # p.classical(0)
         # p.classical('names')
         # classical now back to the default mode
 
+    @pytest.mark.parametrize(
+        'sing, plur',
+        (
+            pytest.param(
+                'about ME',
+                'about US',
+                marks=pytest.mark.xfail(reason='does not keep case'),
+            ),
+            pytest.param(
+                'YOU',
+                'YOU',
+                marks=pytest.mark.xfail(reason='does not keep case'),
+            ),
+        ),
+    )
+    def test_plnoun_retains_case(self, sing, plur):
+        assert inflect.engine()._plnoun(sing) == plur
+
     def test_classical_pl(self):
         p = inflect.engine()
         p.classical()
         for sing, plur in (("brother", "brethren"), ("dogma", "dogmata")):
-            self.assertEqual(p.plural(sing), plur)
+            assert p.plural(sing) == plur
 
     def test__pl_special_verb(self):
         p = inflect.engine()
         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")
+            assert p._pl_special_verb("") is False
+        assert p._pl_special_verb("am") == "are"
+        assert p._pl_special_verb("am", 0) == "are"
+        assert p._pl_special_verb("runs", 0) == "run"
         p.classical(zero=True)
-        self.assertEqual(p._pl_special_verb("am", 0), False)
-        self.assertEqual(p._pl_special_verb("am", 1), "am")
-        self.assertEqual(p._pl_special_verb("am", 2), "are")
-        self.assertEqual(p._pl_special_verb("runs", 0), False)
-        self.assertEqual(p._pl_special_verb("am going to"), "are going to")
-        self.assertEqual(p._pl_special_verb("did"), "did")
-        self.assertEqual(p._pl_special_verb("wasn't"), "weren't")
-        self.assertEqual(p._pl_special_verb("shouldn't"), "shouldn't")
-        self.assertEqual(p._pl_special_verb("bias"), False)
-        self.assertEqual(p._pl_special_verb("news"), False)
-        self.assertEqual(p._pl_special_verb("Jess"), False)
-        self.assertEqual(p._pl_special_verb(" "), False)
-        self.assertEqual(p._pl_special_verb("brushes"), "brush")
-        self.assertEqual(p._pl_special_verb("fixes"), "fix")
-        self.assertEqual(p._pl_special_verb("quizzes"), "quiz")
-        self.assertEqual(p._pl_special_verb("fizzes"), "fizz")
-        self.assertEqual(p._pl_special_verb("dresses"), "dress")
-        self.assertEqual(p._pl_special_verb("flies"), "fly")
-        self.assertEqual(p._pl_special_verb("canoes"), "canoe")
-        self.assertEqual(p._pl_special_verb("horseshoes"), "horseshoe")
-        self.assertEqual(p._pl_special_verb("does"), "do")
+        assert p._pl_special_verb("am", 0) is False
+        assert p._pl_special_verb("am", 1) == "am"
+        assert p._pl_special_verb("am", 2) == "are"
+        assert p._pl_special_verb("runs", 0) is False
+        assert p._pl_special_verb("am going to") == "are going to"
+        assert p._pl_special_verb("did") == "did"
+        assert p._pl_special_verb("wasn't") == "weren't"
+        assert p._pl_special_verb("shouldn't") == "shouldn't"
+        assert p._pl_special_verb("bias") is False
+        assert p._pl_special_verb("news") is False
+        assert p._pl_special_verb("Jess") is False
+        assert p._pl_special_verb(" ") is False
+        assert p._pl_special_verb("brushes") == "brush"
+        assert p._pl_special_verb("fixes") == "fix"
+        assert p._pl_special_verb("quizzes") == "quiz"
+        assert p._pl_special_verb("fizzes") == "fizz"
+        assert p._pl_special_verb("dresses") == "dress"
+        assert p._pl_special_verb("flies") == "fly"
+        assert p._pl_special_verb("canoes") == "canoe"
+        assert p._pl_special_verb("horseshoes") == "horseshoe"
+        assert p._pl_special_verb("does") == "do"
         # TODO: what's a real word to test this case?
-        self.assertEqual(p._pl_special_verb("zzzoes"), "zzzo")
-        self.assertEqual(p._pl_special_verb("runs"), "run")
+        assert p._pl_special_verb("zzzoes") == "zzzo"
+        assert p._pl_special_verb("runs") == "run"
 
     def test__pl_general_verb(self):
         p = inflect.engine()
-        self.assertEqual(p._pl_general_verb("acts"), "act")
-        self.assertEqual(p._pl_general_verb("act"), "act")
-        self.assertEqual(p._pl_general_verb("saw"), "saw")
-        self.assertEqual(p._pl_general_verb("runs", 1), "runs")
-
-    def test__pl_special_adjective(self):
-        p = inflect.engine()
-        self.assertEqual(p._pl_special_adjective("a"), "some")
-        self.assertEqual(p._pl_special_adjective("my"), "our")
-        self.assertEqual(p._pl_special_adjective("John's"), "Johns'")
-        # TODO: original can't handle this. should we handle it?
-        self.TODO(p._pl_special_adjective("JOHN's"), "JOHNS'")
-        # TODO: can't handle capitals
-        self.TODO(p._pl_special_adjective("JOHN'S"), "JOHNS'")
-        self.TODO(p._pl_special_adjective("TUNA'S"), "TUNA'S")
-        self.assertEqual(p._pl_special_adjective("tuna's"), "tuna's")
-        self.assertEqual(p._pl_special_adjective("TUNA's"), "TUNA's")
-        self.assertEqual(p._pl_special_adjective("bad"), False)
-
-    def test_a(self):
+        assert p._pl_general_verb("acts") == "act"
+        assert p._pl_general_verb("act") == "act"
+        assert p._pl_general_verb("saw") == "saw"
+        assert p._pl_general_verb("runs", 1) == "runs"
+
+    @pytest.mark.parametrize(
+        'adj,plur',
+        (
+            ("a", "some"),
+            ("my", "our"),
+            ("John's", "Johns'"),
+            ("tuna's", "tuna's"),
+            ("TUNA's", "TUNA's"),
+            ("bad", False),
+            pytest.param(
+                "JOHN's",
+                "JOHNS'",
+                marks=pytest.mark.xfail(reason='should this be handled?'),
+            ),
+            pytest.param(
+                "JOHN'S",
+                "JOHNS'",
+                marks=pytest.mark.xfail(reason="can't handle capitals"),
+            ),
+            pytest.param(
+                "TUNA'S",
+                "TUNA'S",
+                marks=pytest.mark.xfail(reason="can't handle capitals"),
+            ),
+        ),
+    )
+    def test__pl_special_adjective(self, adj, plur):
         p = inflect.engine()
-        for sing, plur in (
+        assert p._pl_special_adjective(adj) == plur
+
+    @pytest.mark.parametrize(
+        'sing, plur',
+        (
             ("cat", "a cat"),
             ("euphemism", "a euphemism"),
             ("Euler number", "an Euler number"),
@@ -797,30 +806,39 @@ class test(unittest.TestCase):
             ("a cat", "a cat"),
             ("an cat", "a cat"),
             ("a ant", "an ant"),
-        ):
-            self.assertEqual(p.a(sing), plur)
+        ),
+    )
+    def test_a(self, sing, plur):
+        p = inflect.engine()
+        assert p.a(sing) == plur
 
-        self.assertEqual(p.a("cat", 1), "a cat")
-        self.assertEqual(p.a("cat", 2), "2 cat")
+    def test_a_alt(self):
+        p = inflect.engine()
+        assert p.a("cat", 1) == "a cat"
+        assert p.a("cat", 2) == "2 cat"
 
-        self.assertEqual(p.a, p.an)
         with pytest.raises(Exception):
             p.a("")
 
+    def test_a_and_an_same_method(self):
+        assert same_method(inflect.engine.a, inflect.engine.an)
+        p = inflect.engine()
+        assert same_method(p.a, p.an)
+
     def test_no(self):
         p = inflect.engine()
-        self.assertEqual(p.no("cat"), "no cats")
-        self.assertEqual(p.no("cat", count=3), "3 cats")
-        self.assertEqual(p.no("cat", count="three"), "three cats")
-        self.assertEqual(p.no("cat", count=1), "1 cat")
-        self.assertEqual(p.no("cat", count="one"), "one cat")
-        self.assertEqual(p.no("mouse"), "no mice")
+        assert p.no("cat") == "no cats"
+        assert p.no("cat", count=3) == "3 cats"
+        assert p.no("cat", count="three") == "three cats"
+        assert p.no("cat", count=1) == "1 cat"
+        assert p.no("cat", count="one") == "one cat"
+        assert p.no("mouse") == "no mice"
         p.num(3)
-        self.assertEqual(p.no("cat"), "3 cats")
+        assert p.no("cat") == "3 cats"
 
-    def test_prespart(self):
-        p = inflect.engine()
-        for sing, plur in (
+    @pytest.mark.parametrize(
+        'sing, plur',
+        (
             ("runs", "running"),
             ("dies", "dying"),
             ("glues", "gluing"),
@@ -833,19 +851,21 @@ class test(unittest.TestCase):
             ("eats", "eating"),
             ("loves", "loving"),
             ("spies", "spying"),
-        ):
-            self.assertEqual(p.present_participle(sing), plur)
-
-        self.assertEqual(p.present_participle("hoes"), "hoeing")
-        self.assertEqual(p.present_participle("alibis"), "alibiing")
-        self.assertEqual(p.present_participle("is"), "being")
-        self.assertEqual(p.present_participle("are"), "being")
-        self.assertEqual(p.present_participle("had"), "having")
-        self.assertEqual(p.present_participle("has"), "having")
-
-    def test_ordinal(self):
+            ("hoes", "hoeing"),
+            ("alibis", "alibiing"),
+            ("is", "being"),
+            ("are", "being"),
+            ("had", "having"),
+            ("has", "having"),
+        ),
+    )
+    def test_prespart(self, sing, plur):
         p = inflect.engine()
-        for num, numord in (
+        assert p.present_participle(sing) == plur
+
+    @pytest.mark.parametrize(
+        'num, ord',
+        (
             ("1", "1st"),
             ("2", "2nd"),
             ("3", "3rd"),
@@ -865,95 +885,95 @@ class test(unittest.TestCase):
             ("one hundered and one", "one hundered and first"),
             ("zero", "zeroth"),
             ("n", "nth"),  # bonus!
-        ):
-            self.assertEqual(p.ordinal(num), numord)
+        ),
+    )
+    def test_ordinal(self, num, ord):
+        p = inflect.engine()
+        assert p.ordinal(num) == ord
 
     def test_millfn(self):
         p = inflect.engine()
         millfn = p.millfn
-        self.assertEqual(millfn(1), " thousand")
-        self.assertEqual(millfn(2), " million")
-        self.assertEqual(millfn(3), " billion")
-        self.assertEqual(millfn(0), " ")
-        self.assertEqual(millfn(11), " decillion")
-        inflect.STDOUT_ON = False
-        self.assertRaises(NumOutOfRangeError, millfn, 12)
-        inflect.STDOUT_ON = True
+        assert millfn(1) == " thousand"
+        assert millfn(2) == " million"
+        assert millfn(3) == " billion"
+        assert millfn(0) == " "
+        assert millfn(11) == " decillion"
+        with pytest.raises(NumOutOfRangeError):
+            millfn(12)
 
     def test_unitfn(self):
         p = inflect.engine()
         unitfn = p.unitfn
-        self.assertEqual(unitfn(1, 2), "one million")
-        self.assertEqual(unitfn(1, 3), "one billion")
-        self.assertEqual(unitfn(5, 3), "five billion")
-        self.assertEqual(unitfn(5, 0), "five ")
-        self.assertEqual(unitfn(0, 0), " ")
+        assert unitfn(1, 2) == "one million"
+        assert unitfn(1, 3) == "one billion"
+        assert unitfn(5, 3) == "five billion"
+        assert unitfn(5, 0) == "five "
+        assert unitfn(0, 0) == " "
 
     def test_tenfn(self):
         p = inflect.engine()
         tenfn = p.tenfn
-        self.assertEqual(tenfn(3, 1, 2), "thirty-one million")
-        self.assertEqual(tenfn(3, 0, 2), "thirty million")
-        self.assertEqual(tenfn(0, 1, 2), "one million")
-        self.assertEqual(tenfn(1, 1, 2), "eleven million")
-        self.assertEqual(tenfn(1, 0, 2), "ten million")
-        self.assertEqual(tenfn(1, 0, 0), "ten ")
-        self.assertEqual(tenfn(0, 0, 0), " ")
+        assert tenfn(3, 1, 2) == "thirty-one million"
+        assert tenfn(3, 0, 2) == "thirty million"
+        assert tenfn(0, 1, 2) == "one million"
+        assert tenfn(1, 1, 2) == "eleven million"
+        assert tenfn(1, 0, 2) == "ten million"
+        assert tenfn(1, 0, 0) == "ten "
+        assert tenfn(0, 0, 0) == " "
 
     def test_hundfn(self):
         p = inflect.engine()
         hundfn = p.hundfn
         p._number_args = dict(andword="and")
-        self.assertEqual(hundfn(4, 3, 1, 2), "four hundred and thirty-one  million, ")
-        self.assertEqual(hundfn(4, 0, 0, 2), "four hundred  million, ")
-        self.assertEqual(hundfn(4, 0, 5, 2), "four hundred and five  million, ")
-        self.assertEqual(hundfn(0, 3, 1, 2), "thirty-one  million, ")
-        self.assertEqual(hundfn(0, 0, 7, 2), "seven  million, ")
+        assert hundfn(4, 3, 1, 2) == "four hundred and thirty-one  million, "
+        assert hundfn(4, 0, 0, 2) == "four hundred  million, "
+        assert hundfn(4, 0, 5, 2) == "four hundred and five  million, "
+        assert hundfn(0, 3, 1, 2) == "thirty-one  million, "
+        assert hundfn(0, 0, 7, 2) == "seven  million, "
 
     def test_enword(self):
         p = inflect.engine()
         enword = p.enword
-        self.assertEqual(enword("5", 1), "five, ")
+        assert enword("5", 1) == "five, "
         p._number_args = dict(zero="zero", one="one", andword="and")
-        self.assertEqual(enword("0", 1), " zero, ")
-        self.assertEqual(enword("1", 1), " one, ")
-        self.assertEqual(enword("347", 1), "three, four, seven, ")
-
-        self.assertEqual(enword("34", 2), "thirty-four , ")
-        self.assertEqual(enword("347", 2), "thirty-four , seven, ")
-        self.assertEqual(enword("34768", 2), "thirty-four , seventy-six , eight, ")
-        self.assertEqual(enword("1", 2), "one, ")
-        p._number_args["one"] = "single"
-        self.TODO(
-            enword("1", 2), "single, ", "one, "
-        )  # TODO: doesn't use default word for 'one' here
-
-        p._number_args["one"] = "one"
-
-        self.assertEqual(enword("134", 3), " one thirty-four , ")
-
-        self.assertEqual(enword("0", -1), "zero")
-        self.assertEqual(enword("1", -1), "one")
-
-        self.assertEqual(enword("3", -1), "three , ")
-        self.assertEqual(enword("12", -1), "twelve , ")
-        self.assertEqual(enword("123", -1), "one hundred and twenty-three  , ")
-        self.assertEqual(
-            enword("1234", -1), "one thousand, two hundred and thirty-four  , "
+        assert enword("0", 1) == " zero, "
+        assert enword("1", 1) == " one, "
+        assert enword("347", 1) == "three, four, seven, "
+
+        assert enword("34", 2) == "thirty-four , "
+        assert enword("347", 2) == "thirty-four , seven, "
+        assert enword("34768", 2) == "thirty-four , seventy-six , eight, "
+        assert enword("1", 2) == "one, "
+
+        assert enword("134", 3) == " one thirty-four , "
+
+        assert enword("0", -1) == "zero"
+        assert enword("1", -1) == "one"
+
+        assert enword("3", -1) == "three , "
+        assert enword("12", -1) == "twelve , "
+        assert enword("123", -1) == "one hundred and twenty-three  , "
+        assert enword("1234", -1) == "one thousand, two hundred and thirty-four  , "
+        assert (
+            enword("12345", -1) == "twelve thousand, three hundred and forty-five  , "
         )
-        self.assertEqual(
-            enword("12345", -1), "twelve thousand, three hundred and forty-five  , "
+        assert (
+            enword("123456", -1)
+            == "one hundred and twenty-three  thousand, four hundred and fifty-six  , "
         )
-        self.assertEqual(
-            enword("123456", -1),
-            "one hundred and twenty-three  thousand, four hundred and fifty-six  , ",
-        )
-        self.assertEqual(
-            enword("1234567", -1),
-            "one million, two hundred and thirty-four  thousand, "
-            "five hundred and sixty-seven  , ",
+        assert (
+            enword("1234567", -1)
+            == "one million, two hundred and thirty-four  thousand, "
+            "five hundred and sixty-seven  , "
         )
 
+    @pytest.mark.xfail(reason="doesn't use indicated word for 'one'")
+    def test_enword_number_args_override(self):
+        p = inflect.engine()
+        p._number_args["one"] = "single"
+        p.enword("1", 2) == "single, "
+
     def test_numwords(self):
         p = inflect.engine()
         numwords = p.number_to_words
@@ -972,13 +992,13 @@ class test(unittest.TestCase):
             ("10.", "ten point"),
             (".10", "point one zero"),
         ):
-            self.assertEqual(numwords(n), word)
+            assert numwords(n) == word
 
         for n, word, wrongword in (
             # TODO: should be one point two three
             ("1.23", "one point two three", "one point twenty-three"),
         ):
-            self.assertEqual(numwords(n), word)
+            assert numwords(n) == word
 
         for n, txt in (
             (3, "three bottles of beer on the wall"),
@@ -986,214 +1006,211 @@ class test(unittest.TestCase):
             (1, "a solitary bottle of beer on the wall"),
             (0, "no more bottles of beer on the wall"),
         ):
-            self.assertEqual(
+            assert (
                 "{}{}".format(
                     numwords(n, one="a solitary", zero="no more"),
                     p.plural(" bottle of beer on the wall", n),
-                ),
-                txt,
+                )
+                == txt
             )
 
-        self.assertEqual(numwords(0, one="one", zero="zero"), "zero")
-
-        self.assertEqual(numwords("1234"), "one thousand, two hundred and thirty-four")
-        self.assertEqual(
-            numwords("1234", wantlist=True),
-            ["one thousand", "two hundred and thirty-four"],
-        )
-        self.assertEqual(
-            numwords("1234567", wantlist=True),
-            [
-                "one million",
-                "two hundred and thirty-four thousand",
-                "five hundred and sixty-seven",
-            ],
-        )
-        self.assertEqual(numwords("+10", wantlist=True), ["plus", "ten"])
-        self.assertEqual(
-            numwords("1234", andword=""), "one thousand, two hundred thirty-four"
+        assert numwords(0, one="one", zero="zero") == "zero"
+
+        assert numwords("1234") == "one thousand, two hundred and thirty-four"
+        assert numwords("1234", wantlist=True) == [
+            "one thousand",
+            "two hundred and thirty-four",
+        ]
+        assert numwords("1234567", wantlist=True) == [
+            "one million",
+            "two hundred and thirty-four thousand",
+            "five hundred and sixty-seven",
+        ]
+        assert numwords("+10", wantlist=True) == ["plus", "ten"]
+        assert numwords("1234", andword="") == "one thousand, two hundred thirty-four"
+        assert (
+            numwords("1234", andword="plus")
+            == "one thousand, two hundred plus thirty-four"
         )
-        self.assertEqual(
-            numwords("1234", andword="plus"),
-            "one thousand, two hundred plus thirty-four",
-        )
-        self.assertEqual(numwords(p.ordinal("21")), "twenty-first")
-        self.assertEqual(numwords("9", threshold=10), "nine")
-        self.assertEqual(numwords("10", threshold=10), "ten")
-        self.assertEqual(numwords("11", threshold=10), "11")
-        self.assertEqual(numwords("1000", threshold=10), "1,000")
-        self.assertEqual(numwords("123", threshold=10), "123")
-        self.assertEqual(numwords("1234", threshold=10), "1,234")
-        self.assertEqual(numwords("1234.5678", threshold=10), "1,234.5678")
-        self.assertEqual(numwords("1", decimal=None), "one")
-        self.assertEqual(
-            numwords("1234.5678", decimal=None),
-            "twelve million, three hundred and forty-five "
-            "thousand, six hundred and seventy-eight",
+        assert numwords(p.ordinal("21")) == "twenty-first"
+        assert numwords("9", threshold=10) == "nine"
+        assert numwords("10", threshold=10) == "ten"
+        assert numwords("11", threshold=10) == "11"
+        assert numwords("1000", threshold=10) == "1,000"
+        assert numwords("123", threshold=10) == "123"
+        assert numwords("1234", threshold=10) == "1,234"
+        assert numwords("1234.5678", threshold=10) == "1,234.5678"
+        assert numwords("1", decimal=None) == "one"
+        assert (
+            numwords("1234.5678", decimal=None)
+            == "twelve million, three hundred and forty-five "
+            "thousand, six hundred and seventy-eight"
         )
 
-    def test_numwords_group(self):
+    def test_numwords_group_chunking_error(self):
         p = inflect.engine()
-        numwords = p.number_to_words
-        self.assertEqual(numwords("12345", group=2), "twelve, thirty-four, five")
-        # TODO: 'hundred and' missing
-        self.TODO(
-            numwords("12345", group=3),
-            "one hundred and twenty-three",
-            "one twenty-three, forty-five",
-        )
-        self.assertEqual(
-            numwords("123456", group=3), "one twenty-three, four fifty-six"
-        )
-        self.assertEqual(numwords("12345", group=1), "one, two, three, four, five")
-        self.assertEqual(
-            numwords("1234th", group=0, andword="and"),
-            "one thousand, two hundred and thirty-fourth",
-        )
-        self.assertEqual(
-            numwords(p.ordinal("1234"), group=0),
-            "one thousand, two hundred and thirty-fourth",
-        )
-        self.assertEqual(numwords("120", group=2), "twelve, zero")
-        self.assertEqual(numwords("120", group=2, zero="oh", one="unity"), "twelve, oh")
-        # TODO: ignoring 'one' param with group=2
-        self.TODO(
-            numwords("101", group=2, zero="oh", one="unity"), "ten, unity", "ten, one"
-        )
-        self.assertEqual(
-            numwords("555_1202", group=1, zero="oh"),
-            "five, five, five, one, two, oh, two",
-        )
-        self.assertEqual(
-            numwords("555_1202", group=1, one="unity"),
-            "five, five, five, unity, two, zero, two",
-        )
-        self.assertEqual(
-            numwords("123.456", group=1, decimal="mark", one="one"),
-            "one, two, three, mark, four, five, six",
-        )
-
-        inflect.STDOUT_ON = False
-        self.assertRaises(BadChunkingOptionError, numwords, "1234", group=4)
-        inflect.STDOUT_ON = True
+        with pytest.raises(BadChunkingOptionError):
+            p.number_to_words("1234", group=4)
+
+    @pytest.mark.parametrize(
+        'input,kwargs,expect',
+        (
+            ("12345", dict(group=2), "twelve, thirty-four, five"),
+            ("123456", dict(group=3), "one twenty-three, four fifty-six"),
+            ("12345", dict(group=1), "one, two, three, four, five"),
+            (
+                "1234th",
+                dict(group=0, andword="and"),
+                "one thousand, two hundred and thirty-fourth",
+            ),
+            (
+                "1234th",
+                dict(group=0),
+                "one thousand, two hundred and thirty-fourth",
+            ),
+            ("120", dict(group=2), "twelve, zero"),
+            ("120", dict(group=2, zero="oh", one="unity"), "twelve, oh"),
+            (
+                "555_1202",
+                dict(group=1, zero="oh"),
+                "five, five, five, one, two, oh, two",
+            ),
+            (
+                "555_1202",
+                dict(group=1, one="unity"),
+                "five, five, five, unity, two, zero, two",
+            ),
+            (
+                "123.456",
+                dict(group=1, decimal="mark", one="one"),
+                "one, two, three, mark, four, five, six",
+            ),
+            pytest.param(
+                '12345',
+                dict(group=3),
+                'one hundred and twenty-three',
+                marks=pytest.mark.xfail(reason="'hundred and' missing"),
+            ),
+            pytest.param(
+                '101',
+                dict(group=2, zero="oh", one="unity"),
+                "ten, unity",
+                marks=pytest.mark.xfail(reason="ignoring 'one' param with group=2"),
+            ),
+        ),
+    )
+    def test_numwords_group(self, input, kwargs, expect):
+        p = inflect.engine()
+        assert p.number_to_words(input, **kwargs) == expect
 
     def test_wordlist(self):
         p = inflect.engine()
         wordlist = p.join
-        self.assertEqual(wordlist([]), "")
-        self.assertEqual(wordlist(("apple",)), "apple")
-        self.assertEqual(wordlist(("apple", "banana")), "apple and banana")
-        self.assertEqual(
-            wordlist(("apple", "banana", "carrot")), "apple, banana, and carrot"
+        assert wordlist([]) == ""
+        assert wordlist(("apple",)) == "apple"
+        assert wordlist(("apple", "banana")) == "apple and banana"
+        assert wordlist(("apple", "banana", "carrot")) == "apple, banana, and carrot"
+        assert wordlist(("apple", "1,000", "carrot")) == "apple; 1,000; and carrot"
+        assert (
+            wordlist(("apple", "1,000", "carrot"), sep=",")
+            == "apple, 1,000, and carrot"
         )
-        self.assertEqual(
-            wordlist(("apple", "1,000", "carrot")), "apple; 1,000; and carrot"
+        assert (
+            wordlist(("apple", "banana", "carrot"), final_sep="")
+            == "apple, banana and carrot"
         )
-        self.assertEqual(
-            wordlist(("apple", "1,000", "carrot"), sep=","), "apple, 1,000, and carrot"
+        assert (
+            wordlist(("apple", "banana", "carrot"), final_sep=";")
+            == "apple, banana; and carrot"
         )
-        self.assertEqual(
-            wordlist(("apple", "banana", "carrot"), final_sep=""),
-            "apple, banana and carrot",
+        assert (
+            wordlist(("apple", "banana", "carrot"), conj="or")
+            == "apple, banana, or carrot"
         )
-        self.assertEqual(
-            wordlist(("apple", "banana", "carrot"), final_sep=";"),
-            "apple, banana; and carrot",
+
+        assert wordlist(("apple", "banana"), conj=" or ") == "apple  or  banana"
+        assert wordlist(("apple", "banana"), conj="&") == "apple & banana"
+        assert (
+            wordlist(("apple", "banana"), conj="&", conj_spaced=False) == "apple&banana"
         )
-        self.assertEqual(
-            wordlist(("apple", "banana", "carrot"), conj="or"),
-            "apple, banana, or carrot",
+        assert (
+            wordlist(("apple", "banana"), conj="& ", conj_spaced=False)
+            == "apple& banana"
         )
 
-        self.assertEqual(
-            wordlist(("apple", "banana"), conj=" or "), "apple  or  banana"
+        assert (
+            wordlist(("apple", "banana", "carrot"), conj=" or ")
+            == "apple, banana,  or  carrot"
         )
-        self.assertEqual(
-            wordlist(("apple", "banana"), conj="&"), "apple & banana"
-        )  # TODO: want spaces here. Done, report upstream
-        self.assertEqual(
-            wordlist(("apple", "banana"), conj="&", conj_spaced=False), "apple&banana"
+        assert (
+            wordlist(("apple", "banana", "carrot"), conj="+")
+            == "apple, banana, + carrot"
         )
-        self.assertEqual(
-            wordlist(("apple", "banana"), conj="& ", conj_spaced=False), "apple& banana"
+        assert (
+            wordlist(("apple", "banana", "carrot"), conj="&")
+            == "apple, banana, & carrot"
         )
-
-        self.assertEqual(
-            wordlist(("apple", "banana", "carrot"), conj=" or "),
-            "apple, banana,  or  carrot",
+        assert (
+            wordlist(("apple", "banana", "carrot"), conj="&", conj_spaced=False)
+            == "apple, banana,&carrot"
         )
-        self.assertEqual(
-            wordlist(("apple", "banana", "carrot"), conj="+"), "apple, banana, + carrot"
+        assert (
+            wordlist(("apple", "banana", "carrot"), conj=" &", conj_spaced=False)
+            == "apple, banana, &carrot"
         )
-        self.assertEqual(
-            wordlist(("apple", "banana", "carrot"), conj="&"), "apple, banana, & carrot"
-        )  # TODO: want space here. Done, report upstream
-        self.assertEqual(
-            wordlist(("apple", "banana", "carrot"), conj="&", conj_spaced=False),
-            "apple, banana,&carrot",
-        )  # TODO: want space here. Done, report upstream
-        self.assertEqual(
-            wordlist(("apple", "banana", "carrot"), conj=" &", conj_spaced=False),
-            "apple, banana, &carrot",
-        )  # TODO: want space here. Done, report upstream
-
-    def test_print(self):
-        inflect.STDOUT_ON = True
-        inflect.print3("")  # make sure it doesn't crash
-        inflect.STDOUT_ON = False
 
     def test_doc_examples(self):
         p = inflect.engine()
-        self.assertEqual(p.plural_noun("I"), "we")
-        self.assertEqual(p.plural_verb("saw"), "saw")
-        self.assertEqual(p.plural_adj("my"), "our")
-        self.assertEqual(p.plural_noun("saw"), "saws")
-        self.assertEqual(p.plural("was"), "were")
-        self.assertEqual(p.plural("was", 1), "was")
-        self.assertEqual(p.plural_verb("was", 2), "were")
-        self.assertEqual(p.plural_verb("was"), "were")
-        self.assertEqual(p.plural_verb("was", 1), "was")
+        assert p.plural_noun("I") == "we"
+        assert p.plural_verb("saw") == "saw"
+        assert p.plural_adj("my") == "our"
+        assert p.plural_noun("saw") == "saws"
+        assert p.plural("was") == "were"
+        assert p.plural("was", 1) == "was"
+        assert p.plural_verb("was", 2) == "were"
+        assert p.plural_verb("was") == "were"
+        assert p.plural_verb("was", 1) == "was"
 
         for errors, txt in (
             (0, "There were no errors"),
             (1, "There was 1 error"),
             (2, "There were 2 errors"),
         ):
-            self.assertEqual(
+            assert (
                 "There {}{}".format(
                     p.plural_verb("was", errors), p.no(" error", errors)
-                ),
-                txt,
+                )
+                == txt
             )
 
-            self.assertEqual(
+            assert (
                 p.inflect(
                     "There plural_verb('was',%d) no('error',%d)" % (errors, errors)
-                ),
-                txt,
+                )
+                == txt
             )
 
         for num1, num2, txt in ((1, 2, "I saw 2 saws"), (2, 1, "we saw 1 saw")):
-            self.assertEqual(
+            assert (
                 "{}{}{} {}{}".format(
                     p.num(num1, ""),
                     p.plural("I"),
                     p.plural_verb(" saw"),
                     p.num(num2),
                     p.plural_noun(" saw"),
-                ),
-                txt,
+                )
+                == txt
             )
 
-            self.assertEqual(
+            assert (
                 p.inflect(
                     "num(%d, False)plural('I') plural_verb('saw') "
                     "num(%d) plural_noun('saw')" % (num1, num2)
-                ),
-                txt,
+                )
+                == txt
             )
 
-        self.assertEqual(p.a("a cat"), "a cat")
+        assert p.a("a cat") == "a cat"
 
         for word, txt in (
             ("cat", "a cat"),
@@ -1201,38 +1218,11 @@ class test(unittest.TestCase):
             ("ewe", "a ewe"),
             ("hour", "an hour"),
         ):
-            self.assertEqual(
-                p.a("{} {}".format(p.number_to_words(1, one="a"), word)), txt
-            )
+            assert p.a("{} {}".format(p.number_to_words(1, one="a"), word)) == txt
 
         p.num(2)
 
-    def test_deprecation(self):
-        p = inflect.engine()
-        for meth in (
-            "pl",
-            "plnoun",
-            "plverb",
-            "pladj",
-            "sinoun",
-            "prespart",
-            "numwords",
-            "plequal",
-            "plnounequal",
-            "plverbequal",
-            "pladjequal",
-            "wordlist",
-        ):
-            self.assertRaises(DeprecationWarning, getattr, p, meth)
-
     def test_unknown_method(self):
         p = inflect.engine()
-        with self.assertRaises(AttributeError):
+        with pytest.raises(AttributeError):
             p.unknown_method
-
-
-if __name__ == "__main__":
-    try:
-        unittest.main()
-    except SystemExit:
-        pass
diff --git a/tests/test_unicode.py b/tests/test_unicode.py
index d864515..a46c710 100644
--- a/tests/test_unicode.py
+++ b/tests/test_unicode.py
@@ -1,8 +1,7 @@
-import unittest
 import inflect
 
 
-class TestUnicode(unittest.TestCase):
+class TestUnicode:
     """Unicode compatibility test cases"""
 
     def test_unicode_plural(self):
@@ -10,4 +9,4 @@ class TestUnicode(unittest.TestCase):
         engine = inflect.engine()
         unicode_test_cases = {"cliché": "clichés", "ångström": "ångströms"}
         for singular, plural in unicode_test_cases.items():
-            self.assertEqual(plural, engine.plural(singular))
+            assert plural == engine.plural(singular)
diff --git a/towncrier.toml b/towncrier.toml
new file mode 100644
index 0000000..6fa480e
--- /dev/null
+++ b/towncrier.toml
@@ -0,0 +1,2 @@
+[tool.towncrier]
+title_format = "{version}"
diff --git a/tox.ini b/tox.ini
index 5a67821..4b6eaa4 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,8 +1,4 @@
 [tox]
-envlist = python
-minversion = 3.2
-# https://github.com/jaraco/skeleton/issues/6
-tox_pip_extensions_ext_venv_update = true
 toxworkdir={env:TOX_WORK_DIR:.tox}
 
 
@@ -16,6 +12,10 @@ usedevelop = True
 extras =
 	testing
 
+[testenv:pydantic1]
+deps =
+	pydantic < 2
+
 [testenv:docs]
 extras =
 	docs
@@ -25,6 +25,16 @@ commands =
 	python -m sphinx -W --keep-going . {toxinidir}/build/html
 	python -m sphinxlint
 
+[testenv:finalize]
+skip_install = True
+deps =
+	towncrier
+	jaraco.develop >= 7.23
+passenv = *
+commands =
+	python -m jaraco.develop.finalize
+
+
 [testenv:release]
 skip_install = True
 deps =

Debdiff

[The following lists of changes regard files as different if they have different names, permissions or owners.]

Files in second set of .debs but not in first

-rw-r--r--  root/root   /usr/lib/python3/dist-packages/inflect-7.0.0.dist-info/INSTALLER
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/inflect-7.0.0.dist-info/METADATA
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/inflect-7.0.0.dist-info/WHEEL
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/inflect-7.0.0.dist-info/top_level.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/inflect/compat/__init__.py
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/inflect/compat/pydantic.py
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/inflect/compat/pydantic1.py

Files in first set of .debs but not in second

-rw-r--r--  root/root   /usr/lib/python3/dist-packages/inflect-6.0.4.dist-info/METADATA
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/inflect-6.0.4.dist-info/RECORD
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/inflect-6.0.4.dist-info/WHEEL
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/inflect-6.0.4.dist-info/top_level.txt

Control files: lines which differ (wdiff format)

  • Depends: python3-pydantic, python3-typing-extensions, python3:any

More details

Full run details