New Upstream Release - pyhamcrest

Ready changes

Summary

Merged new upstream version: 2.0.4 (was: 2.0.3).

Diff

diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 04de374..0a44639 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -8,6 +8,9 @@ on:
   pull_request:
     branches: ["main", "master"]
 
+  schedule:
+    - cron: "0 6 * * MON" # Every Monday morning
+
   workflow_dispatch:
 
 jobs:
@@ -25,16 +28,19 @@ jobs:
           - "3.7"
           - "3.8"
           - "3.9"
-          - "3.10.0"
-          - "pypy2"
-          - "pypy3"
+          - "3.10"
+          - "3.11.0-beta.5"
+          - "pypy3.9"
         exclude:
           - os: macos-latest
             python-version: pypy3
 
     steps:
-      - uses: "actions/checkout@v2"
-      - uses: "actions/setup-python@v2"
+      - uses: "actions/checkout@v3"
+        with:
+          # We want our tags here
+          fetch-depth: 0
+      - uses: "actions/setup-python@v4"
         with:
           python-version: "${{ matrix.python-version }}"
       - name: "Install dependencies"
@@ -59,8 +65,8 @@ jobs:
       - tests
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v2
-      - uses: actions/setup-python@v2
+      - uses: actions/checkout@v3
+      - uses: actions/setup-python@v4
         with:
           python-version: "3.10"
 
@@ -94,8 +100,11 @@ jobs:
     runs-on: "ubuntu-latest"
 
     steps:
-      - uses: "actions/checkout@v2"
-      - uses: "actions/setup-python@v1"
+      - uses: "actions/checkout@v3"
+        with:
+          # We want our tags here
+          fetch-depth: 0
+      - uses: "actions/setup-python@v4"
         with:
           python-version: "3.10"
 
@@ -105,6 +114,7 @@ jobs:
         if: "${{ env.TEST_PYPI_API_TOKEN != '' }}"
         run: |
           echo "DO_PUBLISH=yes" >> $GITHUB_ENV
+          echo "SETUPTOOLS_SCM_PRETEND_VERSION=0.0.1" >> $GITHUB_ENV
 
       - name: "Install pep517 and twine"
         run: "python -m pip install pep517 twine"
@@ -132,10 +142,10 @@ jobs:
     runs-on: "${{ matrix.os }}"
 
     steps:
-      - uses: "actions/checkout@v2"
-      - uses: "actions/setup-python@v2"
+      - uses: "actions/checkout@v3"
+      - uses: "actions/setup-python@v4"
         with:
-          python-version: "3.9"
+          python-version: "3.10"
       - name: "Install in dev mode"
         run: "python -m pip install -e .[dev]"
       - name: "Import package"
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..66c92ba
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,56 @@
+name: Release
+on:
+  push:
+    tags:
+      - V*
+    branches:
+      - main
+  pull_request:
+    branches:
+      - main
+  workflow_dispatch:
+
+jobs:
+  pure-python-wheel-and-sdist:
+    name: Build a pure Python wheel and source distribution
+    runs-on: ubuntu-latest
+
+    steps:
+      - uses: actions/checkout@v2
+        with:
+          # Fetch all tags; this is needed for hatch-vcs
+          fetch-depth: 0
+
+      - name: Install build dependencies
+        run: python -m pip install --upgrade build
+
+      - name: Build
+        run: python -m build
+
+      - uses: actions/upload-artifact@v2
+        with:
+          name: artifacts
+          path: dist/*
+          if-no-files-found: error
+
+  publish:
+    name: Publish release
+    needs:
+      - pure-python-wheel-and-sdist
+    runs-on: ubuntu-latest
+    if: |
+      (github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags')) ||
+      (github.event_name == 'workflow_dispatch' && startsWith(github.event.ref, 'refs/tags'))
+
+    steps:
+      - uses: actions/download-artifact@v2
+        with:
+          name: artifacts
+          path: dist
+
+      - name: Push build artifacts to PyPI
+        uses: pypa/gh-action-pypi-publish@v1.4.2
+        with:
+          skip_existing: true
+          user: __token__
+          password: ${{ secrets.PYPI_API_TOKEN }}
diff --git a/.gitignore b/.gitignore
index ceb92ea..ed9dbb4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,5 +9,12 @@ dist/
 *~
 .python-version
 .mypy_cache/
+.pytest_cache/
 requirements.txt
 requirements.in
+.envrc
+.direnv/
+doc/_build/
+htmlcov/
+src/hamcrest/_version.py
+.tool-versions
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 5af67ff..951aff7 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -5,12 +5,12 @@ repos:
       - id: check-useless-excludes
 
   - repo: https://github.com/pre-commit/pre-commit-hooks
-    rev: v4.0.1
+    rev: v4.3.0
     hooks:
       - id: debug-statements
 
   - repo: https://github.com/asottile/blacken-docs
-    rev: v1.12.0
+    rev: v1.12.1
     hooks:
       - id: blacken-docs
         # args: ["-l100"]
@@ -26,7 +26,7 @@ repos:
           )
 
   - repo: https://github.com/psf/black
-    rev: 21.12b0
+    rev: 22.6.0
     hooks:
       - id: black
         # args: ["-l100"]
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index b7c9c30..57f1300 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -1,7 +1,33 @@
+Hamcrest 2.0.4 (2022-08-07)
+==========================================
+
+Bugfixes
+--------
+
+- ``has_properties`` now returns ``Matcher[Any]`` type, which addresses type checking errors when nested as a matcher. (`#207 <https://github.com/hamcrest/PyHamcrest/issues/207>`_)
+
+
+`#207 <https://github.com/hamcrest/PyHamcrest/issues/207>`_
+
+  
+Features
+^^^^^^^^
+
+  - Added Python 3.11 testing
+
+`#206 <https://github.com/hamcrest/PyHamcrest/issues/206>`_
+
+  
+ Misc ^^^^
+
+ - `#175 <https://github.com/hamcrest/PyHamcrest/issues/175>`_
+
+  
 2.0.3 (2021-12-12)
 ------------------
  
-  Features ^^^^^^^^
+Features
+^^^^^^^^
 
   - * Adds the tests to the sdist. Fixed by #150
 
@@ -14,15 +40,16 @@
 `#170 <https://github.com/hamcrest/PyHamcrest/issues/170>`_
 
   
- Bugfixes ^^^^^^^^
+Bugfixes
+^^^^^^^^
 
-  - * Test coverage is now submitted to codecov.io.
+- Test coverage is now submitted to codecov.io.
 
     Fixed by #150
 
 `#135 <https://github.com/hamcrest/PyHamcrest/issues/135>`_
- - Change to the ``has_entry()`` matcher - if exactly one key matches, but the value does not, report only the mismatching
-  value.
+
+- Change to the ``has_entry()`` matcher - if exactly one key matches, but the value does not, report only the mismatching value.
 
   Fixed by #157
 
@@ -36,12 +63,6 @@
 
  - `#150 <https://github.com/hamcrest/PyHamcrest/issues/150>`_, `#159 <https://github.com/hamcrest/PyHamcrest/issues/159>`_, `#162 <https://github.com/hamcrest/PyHamcrest/issues/162>`_, `#163 <https://github.com/hamcrest/PyHamcrest/issues/163>`_, `#166 <https://github.com/hamcrest/PyHamcrest/issues/166>`_, `#175 <https://github.com/hamcrest/PyHamcrest/issues/175>`_
 
-  
-   ----
-
-
-Changelog
-=========
 
 Version 2.0.2
 -------------
@@ -86,7 +107,7 @@ Fix #62 - Return result of a deferred call
 Version 1.8.5
 -------------
 
-Fix #56 - incorrect handling of () in is_ matcher
+Fix #56 - incorrect handling of () in ``is_`` matcher
 Fix #60 - correct calling API call with args
 
 Version 1.8.4
@@ -107,7 +128,7 @@ Version 1.8.2
 Version 1.8.1
 -------------
 
-* Added not_ alias for is_not [Matteo Bertini]
+* Added ``not_`` alias for is_not [Matteo Bertini]
 * Added doc directory to the sdist [Alex Brandt]
 
 Version 1.8
@@ -127,34 +148,36 @@ Version 1.7
 -----------
 
 2 Sep 2013 (Version 1.7.2)
+
 * Supported versions
- - As of this version, support for Python 3.1 has been dropped due to no available CI platform.
- - Added support for Python 3.3
+   - As of this version, support for Python 3.1 has been dropped due to no available CI platform.
+   - Added support for Python 3.3
 
 * Bug fixes:
- - string_contains_in_order is now used in the test as it would be in an application, and is properly exported. (Romilly Cocking)
- - Fix mismatch description of containing_inanyorder (David Keijser)
- - added import of stringmatches to text/__init__.py (Eric Scheidemantle)
- - added matches_regexp to __all__ list to library/__init__.py (Eric Scheidemantle)
+   - string_contains_in_order is now used in the test as it would be in an application, and is properly exported. (Romilly Cocking)
+   - Fix mismatch description of containing_inanyorder (David Keijser)
+   - added import of stringmatches to text/__init__.py (Eric Scheidemantle)
+   - added matches_regexp to __all__ list to library/__init__.py (Eric Scheidemantle)
 
 5 Jan 2010 (Version 1.7.1)
+
 * Bug fixes:
- - included a fix by jaimegildesagredo for issue #28 (has_properties was not importable)
- - included a fix by keys for contains_inanyorder
+   - included a fix by jaimegildesagredo for issue #28 (has_properties was not importable)
+   - included a fix by keys for contains_inanyorder
 
 29 Dec 2012
 (All changes by Chris Rose unless otherwise noted.)
 
 * New matchers:
- - matches_regexp matches a regular expression in a string.
- - has_properties matches an object with more than one property.
- - is_empty matches any object with length 0.
+   - matches_regexp matches a regular expression in a string.
+   - has_properties matches an object with more than one property.
+   - is_empty matches any object with length 0.
 
 * Improvements:
- - Can now do matching against old-style classes.
- - Sequence matchers handle generators, as well as actual sequences and
-   pseudo-sequences.
- - README enhancements by ming13
+   - Can now do matching against old-style classes.
+   - Sequence matchers handle generators, as well as actual sequences and
+     pseudo-sequences.
+   - README enhancements by ming13
 
 
 Version 1.6
@@ -181,46 +204,48 @@ Version 1.5
 -----------
 
 29 Apr 2011
+
 * Packaging:
- - Python 3.1 support. Thanks to: Chris Rose
- - Easier installation with bootstrapping. Thanks to: Chris Rose
+   - Python 3.1 support. Thanks to: Chris Rose
+   - Easier installation with bootstrapping. Thanks to: Chris Rose
 
 * Mock integration:
- - "match_equality" wraps a matcher to define equality in terms of satisfying the matcher. This allows Hamcrest matchers to be used in libraries that are not Hamcrest-aware, such as Michael Foord's mock library. Thanks to: Chris Rose
+   - "match_equality" wraps a matcher to define equality in terms of satisfying the matcher. This allows Hamcrest matchers to be used in libraries that are not Hamcrest-aware, such as Michael Foord's mock library. Thanks to: Chris Rose
 
 * New matcher:
- - "string_contains_in_order" matches string containing given list of substrings, in order. Thanks to: Romilly Cocking
+   - "string_contains_in_order" matches string containing given list of substrings, in order. Thanks to: Romilly Cocking
 
 * Improved matchers:
- - For consistency, changed "any_of" and "all_of" to implicitly wrap non-matcher values in EqualTo. Thanks to: Chris Rose
- - Changed "sameInstance" mismatch description to omit address when describing
- None.
+   - For consistency, changed "any_of" and "all_of" to implicitly wrap non-matcher values in EqualTo. Thanks to: Chris Rose
+   - Changed "sameInstance" mismatch description to omit address when describing None.
 
 
 Version 1.4
 -----------
 
 13 Feb 2011
+
 * New matchers:
- - "has_entries" matches dictionary containing key-value pairs satisfying a given list of alternating keys and value matchers.
+   - "has_entries" matches dictionary containing key-value pairs satisfying a given list of alternating keys and value matchers.
 
 * "assert_that" can be invoked with a single boolean argument; the reason message is now optional. This is a convenience replacement for assertTrue. Thanks to: Jeong-Min Lee
 
 * Improved descriptions:
- - Reverted 1.3 change: Describe None as "<None>" after all, since it is an object.
- - "is_" no longer says "is ..." in its description, but just lets the inner description pass through.
- - Consistently use articles to begin descriptions, such as "a sequence containing" instead of "sequence containing".
+   - Reverted 1.3 change: Describe None as "<None>" after all, since it is an object.
+   - "``is_``" no longer says "is ..." in its description, but just lets the inner description pass through.
+   - Consistently use articles to begin descriptions, such as "a sequence containing" instead of "sequence containing".
 
 
 Version 1.3
 -----------
 
 04 Feb 2011
+
 * PyHamcrest is now compatible with Python 3! To install PyHamcrest on Python 3:
-  - Install the "distribute" package, http://pypi.python.org/pypi/distribute
-  - Run "python3 setup.py install"
-  Unit tests are not converted by the install procedure. Run "2to3 -nw ." separately to convert them. You may discover import statements in the __init__.py files (and one in core/base_description.py) that need dot prefixes.
-  Thanks to: Jeong-Min Lee
+   - Install the "distribute" package, http://pypi.python.org/pypi/distribute
+   - Run "python3 setup.py install"
+     Unit tests are not converted by the install procedure. Run "2to3 -nw ." separately to convert them. You may discover import statements in the __init__.py files (and one in core/base_description.py) that need dot prefixes.
+     Thanks to: Jeong-Min Lee
 
 * Improved descriptions and mismatch descriptions of several matchers, including:
   - Fixed "contains" and "contains_inanyorder" to describe mismatch if item is not a sequence.
@@ -236,15 +261,16 @@ Version 1.2.1
 -------------
 
 04 Jan 2011
+
 * Fixed "assert_that" to describe the diagnosis of the mismatch, not just the
 mismatched value. PyHamcrest will now give even more useful information.
 
 * Expanded BaseDescription.append_description_of to handle all types of values, not just self-describing values.
 
 * Deprecated:
- - Description.append_value no longer needed; call append_description_of instead.
- - BaseDescription.append_value_list no longer needed; call append_list instead.
- - SelfDescribingValue no longer needed.
+   - Description.append_value no longer needed; call append_description_of instead.
+   - BaseDescription.append_value_list no longer needed; call append_list instead.
+   - SelfDescribingValue no longer needed.
 
 1.2.1 fixes to 1.2:
 - Corrected manifest so install works. Thanks to: Jeong-Min Lee
@@ -254,9 +280,10 @@ Version 1.1
 -----------
 
 28 Dec 2010
+
 * New matchers:
-  - "contains" matches sequence containing matching items in order.
-  - "contains_inanyorder" matches sequence containing matching items in any order.
+    - "contains" matches sequence containing matching items in order.
+    - "contains_inanyorder" matches sequence containing matching items in any order.
 
 * Added Sphinx documentation support.
 
diff --git a/README.rst b/README.rst
index 9be6c1b..55f6a51 100644
--- a/README.rst
+++ b/README.rst
@@ -55,7 +55,7 @@ the standard set of matchers:
 
 .. code:: python
 
- from hamcrest import *
+ from hamcrest import assert_that, equal_to
  import unittest
 
 
@@ -266,7 +266,7 @@ could use it in our test by importing the factory function ``on_a_saturday``:
 
 .. code:: python
 
- from hamcrest import *
+ from hamcrest import assert_that, is_
  import unittest
  from isgivendayofweek import on_a_saturday
 
diff --git a/debian/changelog b/debian/changelog
index 97e2312..6f456bb 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+pyhamcrest (2.0.4-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Sat, 01 Apr 2023 06:05:12 -0000
+
 pyhamcrest (2.0.3-2) unstable; urgency=medium
 
   [ Debian Janitor ]
diff --git a/doc/tutorial.rst b/doc/tutorial.rst
index baa3d6d..13ded0c 100644
--- a/doc/tutorial.rst
+++ b/doc/tutorial.rst
@@ -29,7 +29,7 @@ We'll start by writing a very simple PyUnit test, but instead of using PyUnit's
 :py:func:`~hamcrest.core.matcher_assert.assert_that` construct and the standard
 set of matchers::
 
-    from hamcrest import *
+    from hamcrest import assert_that, equal_to
     import unittest
 
     class BiscuitTest(unittest.TestCase):
@@ -81,7 +81,7 @@ assure that the right issue was found::
 
     assert_that(calling(parse, bad_data), raises(ValueError))
 
-    assert_that(calling(translate).with_(curse_words), raises(LanguageError, "\w+very naughty"))
+    assert_that(calling(translate).with_args(curse_words), raises(LanguageError, "\w+very naughty"))
 
     assert_that(broken_function, raises(Exception))
 
diff --git a/pyproject.toml b/pyproject.toml
index 859a8e2..e1045b0 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,7 +1,105 @@
 [build-system]
-requires = ["setuptools>=40.6.0", "wheel"]
-build-backend = "setuptools.build_meta"
+requires = ["hatchling", "hatch-vcs"]
+build-backend = "hatchling.build"
 
+[project]
+name = "PyHamcrest"
+description = "Hamcrest framework for matcher objects"
+readme = "README.rst"
+requires-python = ">= 3.6"
+license = { file = "LICENSE.txt" }
+keywords = [
+  "hamcrest",
+  "matchers",
+  "pyunit",
+  "unit",
+  "test",
+  "testing",
+  "unittest",
+  "unittesting",
+]
+authors = [
+  { name = "Chris Rose", email="offline@offby1.net" },
+  { name = "Simon Brunning" },
+  { name = "Jon Reid" },
+]
+classifiers = [
+  "Development Status :: 5 - Production/Stable",
+  "Environment :: Console",
+  "Intended Audience :: Developers",
+  "License :: OSI Approved :: BSD License",
+  "Natural Language :: English",
+  "Operating System :: OS Independent",
+  "Programming Language :: Python :: 3",
+  "Programming Language :: Python :: 3.6",
+  "Programming Language :: Python :: 3.7",
+  "Programming Language :: Python :: 3.8",
+  "Programming Language :: Python :: 3.9",
+  "Programming Language :: Python :: 3.10",
+  "Programming Language :: Python :: 3.11",
+  "Programming Language :: Python :: Implementation :: CPython",
+  "Programming Language :: Python :: Implementation :: Jython",
+  "Programming Language :: Python :: Implementation :: PyPy",
+  "Topic :: Software Development",
+  "Topic :: Software Development :: Quality Assurance",
+  "Topic :: Software Development :: Testing",
+]
+dynamic = ["version"]
+
+[project.optional-dependencies]
+docs = ["sphinx~=4.0", "alabaster~=0.7"]
+tests = [
+  "pytest>=5.0",
+  "pytest-sugar",
+  "pytest-xdist",
+  "coverage[toml]",
+  # No point on Pypy thanks to https://github.com/python/typed_ast/issues/111
+  "pytest-mypy-plugins; platform_python_implementation != 'PyPy'",
+  # Can't use 0.940: https://github.com/python/mypy/issues/12339
+  "mypy!=0.940; platform_python_implementation != 'PyPy'",
+  "types-mock",
+  "dataclasses; python_version<'3.7'",
+  "types-dataclasses; python_version<'3.7'",
+]
+tests-numpy = [
+  "PyHamcrest[tests]",
+  "numpy",
+]
+dev = [
+  "PyHamcrest[docs,tests]",
+  "towncrier",
+  "twine",
+  "pytest-mypy",
+  "flake8",
+  "black",
+  "tox",
+  "tox-asdf",
+]
+
+[project.urls]
+History = "https://github.com/hamcrest/PyHamcrest/blob/main/CHANGELOG.rst"
+Source = "https://github.com/hamcrest/PyHamcrest/"
+Issues = "https://github.com/hamcrest/PyHamcrest/issues"
+
+[tool.hatch.version]
+source = "vcs"
+
+[tool.hatch.build.hooks.vcs]
+version-file = "src/hamcrest/_version.py"
+
+[tool.hatch.build.targets.sdist]
+exclude = [
+  "/changelog.d/*.rst",
+  "/release.sh",
+  "/.github",
+]
+[tool.hatch.build.targets.wheel]
+exclude = [
+  "/examples",
+]
+packages = [
+  "src/hamcrest",
+]
 
 [tool.coverage.run]
 parallel = true
@@ -45,13 +143,9 @@ profile = "hamcrests"
 known_first_party = "hamcrest"
 known_third_party = ["hypothesis", "pytest", "setuptools", "six"]
 
-
 [tool.towncrier]
-package = "hamcrest"
-package_dir = "src"
-filename = "CHANGELOG.rst"
-template = "changelog.d/towncrier_template.rst"
-issue_format = "`#{issue} <https://github.com/hamcrest/PyHamcrest/issues/{issue}>`_"
-directory = "changelog.d"
-title_format = "{version} ({project_date})"
-underlines = ["-", "^"]
+    package = "hamcrest"
+    package_dir = "src"
+    filename = "CHANGELOG.rst"
+    directory = "changelog.d"
+    issue_format = "`#{issue} <https://github.com/hamcrest/PyHamcrest/issues/{issue}>`_"
diff --git a/setup.py b/setup.py
deleted file mode 100755
index b68895b..0000000
--- a/setup.py
+++ /dev/null
@@ -1,104 +0,0 @@
-import os
-import re
-
-from setuptools import find_packages, setup
-
-# need to kill off link if we're in docker builds
-if os.environ.get("PYTHON_BUILD_DOCKER", None) == "true":
-    del os.link
-
-os.chdir(os.path.dirname(os.path.realpath(__file__)))
-
-
-def read(fname):
-    return open(fname).read()
-
-
-# On Python 3, we can't "from hamcrest import __version__" (get ImportError),
-# so we extract the variable assignment and execute it ourselves.
-fh = open("src/hamcrest/__init__.py")
-
-# this will be overridden
-__version__ = None
-try:
-    for line in fh:
-        if re.match("__version__.*", line):
-            exec(line)
-
-finally:
-    if fh:
-        fh.close()
-
-assert __version__ is not None
-
-REQUIREMENTS_DOCS = ["sphinx~=3.0", "alabaster~=0.7"]
-TESTS_BASIC = [
-    "pytest>=5.0",
-    "pytest-sugar",
-    "pytest-xdist",
-    "coverage[toml]",
-    # No point on Pypy thanks to https://github.com/python/typed_ast/issues/111
-    "pytest-mypy-plugins; platform_python_implementation != 'PyPy'",
-    "types-mock",
-]
-TESTS_NUMPY = ["numpy"]
-DEV_TOOLS = [
-    "towncrier",
-    "twine",
-    "pytest-mypy",
-    "flake8",
-    "black",
-    "tox",
-    "tox-pyenv",
-    "tox-asdf",
-]
-
-
-params = dict(
-    name="PyHamcrest",
-    version=__version__,  # flake8:noqa
-    author="Chris Rose",
-    author_email="offline@offby1.net",
-    description="Hamcrest framework for matcher objects",
-    license="New BSD",
-    platforms=["All"],
-    keywords="hamcrest matchers pyunit unit test testing unittest unittesting",
-    url="https://github.com/hamcrest/PyHamcrest",
-    download_url="http://pypi.python.org/packages/source/P/PyHamcrest/PyHamcrest-%s.tar.gz"
-    % __version__,
-    packages=find_packages("src"),
-    package_dir={"": "src"},
-    package_data={"hamcrest": ["py.typed"]},
-    provides=["hamcrest"],
-    long_description=read("README.rst"),
-    long_description_content_type="text/x-rst",
-    python_requires=">=3.5",
-    install_requires=[],
-    extras_require={
-        "docs": REQUIREMENTS_DOCS,
-        "tests": TESTS_BASIC,
-        "tests-numpy": TESTS_BASIC + TESTS_NUMPY,
-        "dev": REQUIREMENTS_DOCS + TESTS_BASIC + DEV_TOOLS,
-    },
-    classifiers=[
-        "Development Status :: 5 - Production/Stable",
-        "Environment :: Console",
-        "Intended Audience :: Developers",
-        "License :: OSI Approved :: BSD License",
-        "Natural Language :: English",
-        "Operating System :: OS Independent",
-        "Programming Language :: Python :: 3",
-        "Programming Language :: Python :: 3.6",
-        "Programming Language :: Python :: 3.7",
-        "Programming Language :: Python :: 3.8",
-        "Programming Language :: Python :: Implementation :: CPython",
-        "Programming Language :: Python :: Implementation :: Jython",
-        "Programming Language :: Python :: Implementation :: PyPy",
-        "Topic :: Software Development",
-        "Topic :: Software Development :: Quality Assurance",
-        "Topic :: Software Development :: Testing",
-    ],
-)
-
-all_params = dict(params.items())
-setup(**all_params)
diff --git a/src/hamcrest/__init__.py b/src/hamcrest/__init__.py
index b307591..b40f701 100644
--- a/src/hamcrest/__init__.py
+++ b/src/hamcrest/__init__.py
@@ -1,8 +1,9 @@
 from hamcrest.core import *
 from hamcrest.library import *
 from hamcrest import core, library
+from hamcrest._version import version
 
-__version__ = "2.0.3"
+__version__ = version
 __author__ = "Chris Rose"
 __copyright__ = "Copyright 2020 hamcrest.org"
 __license__ = "BSD, see License.txt"
diff --git a/src/hamcrest/core/assert_that.py b/src/hamcrest/core/assert_that.py
index c8882bb..a31cf3a 100644
--- a/src/hamcrest/core/assert_that.py
+++ b/src/hamcrest/core/assert_that.py
@@ -16,16 +16,16 @@ T = TypeVar("T")
 
 
 @overload
-def assert_that(actual: T, matcher: Matcher[T], reason: str = "") -> None:
+def assert_that(actual_or_assertion: T, matcher: Matcher[T], reason: str = "") -> None:
     ...
 
 
 @overload
-def assert_that(assertion: bool, reason: str = "") -> None:
+def assert_that(actual_or_assertion: bool, reason: str = "") -> None:
     ...
 
 
-def assert_that(actual, matcher=None, reason=""):
+def assert_that(actual_or_assertion, matcher=None, reason=""):
     """Asserts that actual value satisfies matcher. (Can also assert plain
     boolean condition.)
 
@@ -55,11 +55,11 @@ def assert_that(actual, matcher=None, reason=""):
 
     """
     if isinstance(matcher, Matcher):
-        _assert_match(actual=actual, matcher=matcher, reason=reason)
+        _assert_match(actual=actual_or_assertion, matcher=matcher, reason=reason)
     else:
-        if isinstance(actual, Matcher):
-            warnings.warn("arg1 should be boolean, but was {}".format(type(actual)))
-        _assert_bool(assertion=cast(bool, actual), reason=cast(str, matcher))
+        if isinstance(actual_or_assertion, Matcher):
+            warnings.warn("arg1 should be boolean, but was {}".format(type(actual_or_assertion)))
+        _assert_bool(assertion=cast(bool, actual_or_assertion), reason=cast(str, matcher))
 
 
 def _assert_match(actual: T, matcher: Matcher[T], reason: str) -> None:
diff --git a/src/hamcrest/library/integration/__init__.py b/src/hamcrest/library/integration/__init__.py
index b28371c..e87d6e1 100644
--- a/src/hamcrest/library/integration/__init__.py
+++ b/src/hamcrest/library/integration/__init__.py
@@ -5,3 +5,5 @@ from .match_equality import match_equality
 __author__ = "Jon Reid"
 __copyright__ = "Copyright 2011 hamcrest.org"
 __license__ = "BSD, see License.txt"
+
+__all__ = ["match_equality"]
diff --git a/src/hamcrest/library/number/__init__.py b/src/hamcrest/library/number/__init__.py
index ca9b151..85f4cf6 100644
--- a/src/hamcrest/library/number/__init__.py
+++ b/src/hamcrest/library/number/__init__.py
@@ -11,3 +11,11 @@ from .ordering_comparison import (
 __author__ = "Jon Reid"
 __copyright__ = "Copyright 2011 hamcrest.org"
 __license__ = "BSD, see License.txt"
+
+__all__ = [
+    "close_to",
+    "greater_than",
+    "greater_than_or_equal_to",
+    "less_than",
+    "less_than_or_equal_to",
+]
diff --git a/src/hamcrest/library/number/ordering_comparison.py b/src/hamcrest/library/number/ordering_comparison.py
index 6c6275d..f121caf 100644
--- a/src/hamcrest/library/number/ordering_comparison.py
+++ b/src/hamcrest/library/number/ordering_comparison.py
@@ -22,7 +22,10 @@ class OrderingComparison(BaseMatcher[Any]):
         self.comparison_description = comparison_description
 
     def _matches(self, item: Any) -> bool:
-        return self.comparison_function(item, self.value)
+        try:
+            return self.comparison_function(item, self.value)
+        except TypeError:
+            return False
 
     def describe_to(self, description: Description) -> None:
         description.append_text("a value ").append_text(self.comparison_description).append_text(
diff --git a/src/hamcrest/library/object/__init__.py b/src/hamcrest/library/object/__init__.py
index af84d7e..e015671 100644
--- a/src/hamcrest/library/object/__init__.py
+++ b/src/hamcrest/library/object/__init__.py
@@ -7,3 +7,10 @@ from .hasstring import has_string
 __author__ = "Jon Reid"
 __copyright__ = "Copyright 2011 hamcrest.org"
 __license__ = "BSD, see License.txt"
+
+__all__ = [
+    "has_length",
+    "has_properties",
+    "has_property",
+    "has_string",
+]
diff --git a/src/hamcrest/library/object/hasproperty.py b/src/hamcrest/library/object/hasproperty.py
index b27036d..ea2519f 100644
--- a/src/hamcrest/library/object/hasproperty.py
+++ b/src/hamcrest/library/object/hasproperty.py
@@ -94,19 +94,19 @@ def has_property(name: str, match: Union[None, Matcher[V], V] = None) -> Matcher
 
 # Keyword argument form
 @overload
-def has_properties(**keys_valuematchers: Union[Matcher[V], V]) -> Matcher[object]:
+def has_properties(**keys_valuematchers: Union[Matcher[V], V]) -> Matcher[Any]:
     ...
 
 
 # Name to matcher dict form
 @overload
-def has_properties(keys_valuematchers: Mapping[str, Union[Matcher[V], V]]) -> Matcher[object]:
+def has_properties(keys_valuematchers: Mapping[str, Union[Matcher[V], V]]) -> Matcher[Any]:
     ...
 
 
 # Alternating name/matcher form
 @overload
-def has_properties(*keys_valuematchers: Any) -> Matcher[object]:
+def has_properties(*keys_valuematchers: Any) -> Matcher[Any]:
     ...
 
 
diff --git a/src/hamcrest/library/text/__init__.py b/src/hamcrest/library/text/__init__.py
index 291ac31..19e46c4 100644
--- a/src/hamcrest/library/text/__init__.py
+++ b/src/hamcrest/library/text/__init__.py
@@ -11,3 +11,13 @@ from .stringstartswith import starts_with
 __author__ = "Jon Reid"
 __copyright__ = "Copyright 2011 hamcrest.org"
 __license__ = "BSD, see License.txt"
+
+__all__ = [
+    "contains_string",
+    "ends_with",
+    "equal_to_ignoring_case",
+    "equal_to_ignoring_whitespace",
+    "matches_regexp",
+    "starts_with",
+    "string_contains_in_order",
+]
diff --git a/tests/hamcrest_unit_test/collection/is_empty_test.py b/tests/hamcrest_unit_test/collection/is_empty_test.py
index 84f8eb8..44b5292 100644
--- a/tests/hamcrest_unit_test/collection/is_empty_test.py
+++ b/tests/hamcrest_unit_test/collection/is_empty_test.py
@@ -19,7 +19,7 @@ class EmptyCollectionTest(MatcherTest):
         matcher = empty()
         self.assert_matches("empty tuple", matcher, ())
         self.assert_matches("empty list", matcher, [])
-        self.assert_matches("emtpy dictionary", matcher, {})
+        self.assert_matches("empty dictionary", matcher, {})
 
     def testReturnsTrueForEmptyCollectionLike(self):
         matcher = empty()
@@ -29,7 +29,7 @@ class EmptyCollectionTest(MatcherTest):
         matcher = empty()
         self.assert_does_not_match("non-empty tuple", matcher, (1,))
         self.assert_does_not_match("non-empty list", matcher, [1])
-        self.assert_does_not_match("emtpy dictionary", matcher, {1: 2})
+        self.assert_does_not_match("empty dictionary", matcher, {1: 2})
 
     def testReturnsFalseForNonEmptyCollectionLike(self):
         matcher = empty()
diff --git a/tests/hamcrest_unit_test/collection/issequence_containinginanyorder_test.py b/tests/hamcrest_unit_test/collection/issequence_containinginanyorder_test.py
index 5c21fbb..b36d192 100644
--- a/tests/hamcrest_unit_test/collection/issequence_containinginanyorder_test.py
+++ b/tests/hamcrest_unit_test/collection/issequence_containinginanyorder_test.py
@@ -1,5 +1,6 @@
 import unittest
 
+from hamcrest import greater_than
 from hamcrest.core.core.isequal import equal_to
 from hamcrest.library.collection.issequence_containinginanyorder import contains_inanyorder
 from hamcrest_unit_test.matcher_test import MatcherTest
@@ -84,6 +85,16 @@ class IsSequenceContainingInAnyOrderBase(object):
             "no item matches: <2> in [<3>, <1>]", matcher, self._sequence(3, 1)
         )
 
+    def testIncomparableTypes(self):
+        self.assert_matches("Incomparable types", contains_inanyorder(*[4, "a"]), ["a", 4])
+
+    def testIncomparableTypesInNestedMatcher(self):
+        self.assert_matches(
+            "Incomparable types in nested matcher",
+            contains_inanyorder(*[greater_than(0), "a"]),
+            ["a", 4],
+        )
+
 
 class IsConcreteSequenceContainingInAnyOrderTest(
     MatcherTest, IsSequenceContainingInAnyOrderBase, SequenceForm
diff --git a/tests/hamcrest_unit_test/number/ordering_comparison_test.py b/tests/hamcrest_unit_test/number/ordering_comparison_test.py
index 76143df..d944519 100644
--- a/tests/hamcrest_unit_test/number/ordering_comparison_test.py
+++ b/tests/hamcrest_unit_test/number/ordering_comparison_test.py
@@ -67,6 +67,9 @@ class OrderingComparisonTest(MatcherTest):
         self.assert_describe_mismatch("was <0>", greater_than_or_equal_to(1), 0)
         self.assert_describe_mismatch("was <2>", less_than_or_equal_to(1), 2)
 
+    def testIncomparableTypes(self):
+        self.assert_does_not_match("incomparable types", greater_than(1), "a")
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/tests/type-hinting/library/collection/test_empty.yml b/tests/type-hinting/library/collection/test_empty.yml
index 5264802..cf815c7 100644
--- a/tests/type-hinting/library/collection/test_empty.yml
+++ b/tests/type-hinting/library/collection/test_empty.yml
@@ -5,4 +5,4 @@
     from hamcrest import assert_that, is_, empty
 
     assert_that([], empty())
-    assert_that(99, empty())  # E: Cannot infer type argument 1 of "assert_that"
\ No newline at end of file
+    assert_that(99, empty())  # E: Cannot infer type argument 1 of "assert_that"
diff --git a/tests/type-hinting/library/collection/test_generics.yml b/tests/type-hinting/library/collection/test_generics.yml
new file mode 100644
index 0000000..4666d5f
--- /dev/null
+++ b/tests/type-hinting/library/collection/test_generics.yml
@@ -0,0 +1,27 @@
+- case: valid_has_items_has_properties
+  skip: platform.python_implementation() == "PyPy"
+  main: |
+    from dataclasses import dataclass
+    from hamcrest import assert_that, has_items, has_properties
+
+    @dataclass
+    class Example:
+      name: str
+
+    items = [Example("dave"), Example("wave")]
+
+    a = assert_that(items, has_items(has_properties(name="dave")))
+
+- case: valid_has_item_has_properties
+  skip: platform.python_implementation() == "PyPy"
+  main: |
+    from dataclasses import dataclass
+    from hamcrest import assert_that, has_item, has_properties
+
+    @dataclass
+    class Example:
+      name: str
+
+    items = [Example("dave"), Example("wave")]
+    matcher = has_item(has_properties(name="dave"))
+    a = assert_that(items, matcher)
diff --git a/tox.ini b/tox.ini
index 037665a..6c0c1fd 100644
--- a/tox.ini
+++ b/tox.ini
@@ -17,6 +17,7 @@ python =
     3.8: py38, py38-numpy
     3.9: py39, py39-numpy, lint, manifest, typing, changelog, docs
     3.10: py310
+    3.11: py311
     pypy-2: pypy2
     pypy-3: pypy3
 
@@ -96,6 +97,16 @@ setenv =
 extras = {env:TOX_AP_TEST_EXTRAS:tests}
 commands = coverage run -m pytest {posargs}
 
+[testenv:py311]
+# Python 3.6+ has a number of compile-time warnings on invalid string escapes.
+# PYTHONWARNINGS=d and --no-compile below make them visible during the Tox run.
+basepython = python3.11
+install_command = pip install --no-compile {opts} {packages}
+setenv =
+    PYTHONWARNINGS=d
+extras = {env:TOX_AP_TEST_EXTRAS:tests}
+commands = coverage run -m pytest {posargs}
+
 [testenv:coverage-report]
 basepython = python3.9
 skip_install = true
@@ -104,7 +115,6 @@ commands =
     coverage combine
     coverage report
 
-
 [testenv:lint]
 basepython = python3.9
 skip_install = true
@@ -125,9 +135,11 @@ commands =
 
 [testenv:manifest]
 basepython = python3.9
-deps = check-manifest
+deps =
+    check-manifest
+    setuptools-scm
 skip_install = true
-commands = check-manifest
+commands = check-manifest --ignore src/hamcrest/_version.py
 
 
 [testenv:pypi-description]

More details

Full run details

Historical runs