anys


Nameanys JSON
Version 0.3.0 PyPI version JSON
download
home_pagehttps://github.com/jwodder/anys
SummaryMatchers for pytest
upload_time2023-09-12 19:57:49
maintainer
docs_urlNone
authorJohn Thorvald Wodder II
requires_python>=3.7
licenseMIT
keywords comparison matchers pytest testing unit test
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            .. image:: http://www.repostatus.org/badges/latest/active.svg
    :target: http://www.repostatus.org/#active
    :alt: Project Status: Active — The project has reached a stable, usable
          state and is being actively developed.

.. image:: https://github.com/jwodder/anys/workflows/Test/badge.svg?branch=master
    :target: https://github.com/jwodder/anys/actions?workflow=Test
    :alt: CI Status

.. image:: https://codecov.io/gh/jwodder/anys/branch/master/graph/badge.svg
    :target: https://codecov.io/gh/jwodder/anys

.. image:: https://img.shields.io/pypi/pyversions/anys.svg
    :target: https://pypi.org/project/anys/

.. image:: https://img.shields.io/conda/vn/conda-forge/anys.svg
    :target: https://anaconda.org/conda-forge/anys
    :alt: Conda Version

.. image:: https://img.shields.io/github/license/jwodder/anys.svg
    :target: https://opensource.org/licenses/MIT
    :alt: MIT License

`GitHub <https://github.com/jwodder/anys>`_
| `PyPI <https://pypi.org/project/anys/>`_
| `Issues <https://github.com/jwodder/anys/issues>`_
| `Changelog <https://github.com/jwodder/anys/blob/master/CHANGELOG.md>`_

``anys`` provides matchers for pytest_-style assertions.  What's a "matcher,"
you say?  Well, say you're writing a unit test and you want to assert that a
given object contains the correct values.  Normally, you'd just write:

.. code:: python

    assert foo == {
        "widgets": 42,
        "name": "Alice",
        "subfoo": {
            "created_at": "2021-06-24T18:41:59Z",
            "name": "Bob",
            "widgets": 23,
        }
    }

But wait!  What if the value of ``foo["subfoo"]["created_at"]`` can't be
determined in advance, but you still need to check that it's a valid timestamp?
You'd have to compare everything in ``foo`` other than that one field to the
expected values and then separately check the timestamp field for validity.
This is where matchers come in: they're magic objects that compare equal to any
& all values that meet given criteria.  For the case above, ``anys`` allows you
to just write:

.. code:: python

    from anys import ANY_DATETIME_STR

    assert foo == {
        "widgets": 42,
        "name": "Alice",
        "subfoo": {
            "created_at": ANY_DATETIME_STR,
            "name": "Bob",
            "widgets": 23,
        }
    }

and the assertion will do what you mean.

.. _pytest: https://docs.pytest.org

Installation
============
``anys`` requires Python 3.7 or higher.  Just use `pip <https://pip.pypa.io>`_
for Python 3 (You have pip, right?) to install it::

    python3 -m pip install anys


API
===

``anys`` provides the following classes & constants for matching against values
meeting certain criteria.  Matching is performed by comparing a value against
an ``anys`` matcher with ``==``, either directly or as a result of comparing
two larger structures with ``==``.

If a comparison raises a ``TypeError`` or ``ValueError`` (say, because you
evaluated ``42 == AnyMatch(r'\d+')``, which tries to match a regex against an
integer), the exception is suppressed, and the comparison evaluates to
``False``; all other exceptions are propagated out.

``anys`` matchers can be combined using the ``&`` operator to produce new
matchers that require both operands to succeed; for example, ``AnyGT(23) &
AnyLT(42)`` will match any number between 23 and 42, exclusive, and nothing
else.

``anys`` matchers can be combined using the ``|`` operator to produce new
matchers that require at least one of the operands to succeed; for example,
``ANY_INT | ANY_STR`` will match any value that is an ``int`` or a ``str``.

Classes
-------

Note that, unless stated otherwise, ``anys`` class constructors cannot take
``anys`` matchers as arguments.

.. code:: python

    AnyContains(key: Any, /)

A matcher that matches any value for which ``key in value`` is true.  If
``key`` is an ``anys`` matcher, ``value == AnyContains(key)`` will instead be
evaluated by iterating through the elements of ``value`` and checking whether
any match ``key``.

.. code:: python

    AnyFullmatch(pattern: Union[AnyStr, re.Pattern[AnyStr]], /)

A matcher that matches any string ``s`` for which ``re.fullmatch(pattern, s)``
succeeds

.. code:: python

    AnyFunc(func: Callable, /)

A matcher that matches any value ``x`` for which ``func(x)`` is true.  If
``func(x)`` raises a ``TypeError`` or ``ValueError``, it will be suppressed,
and ``x == AnyFunc(func)`` will evaluate to ``False``.  All other exceptions
are propagated out.

.. code:: python

    AnyGE(bound: Any, /)

A matcher that matches any value greater than or equal to ``bound``

.. code:: python

    AnyGT(bound: Any, /)

A matcher that matches any value greater than ``bound``

.. code:: python

    AnyIn(iterable: Iterable, /)

A matcher that matches any value that equals or matches an element of
``iterable`` (which may contain ``anys`` matchers).  Note that, if ``iterable``
is a string, only individual characters in the string will match; to match
substrings, use ``AnySubstr()`` instead.

.. code:: python

    AnyInstance(classinfo, /)

A matcher that matches any value that is an instance of ``classinfo``.
``classinfo`` can be either a type or a tuple of types (or, starting in Python
3.10, a ``Union`` of types).

A number of pre-composed ``AnyInstance()`` values are provided as constants for
your convenience; see "Constants_" below.

.. code:: python

    AnyLE(bound: Any, /)

A matcher that matches any value less than or equal to ``bound``

.. code:: python

    AnyLT(bound: Any, /)

A matcher that matches any value less than ``bound``

.. code:: python

    AnyMatch(pattern: Union[AnyStr, re.Pattern[AnyStr]], /)

A matcher that matches any string ``s`` for which ``re.match(pattern, s)``
succeeds

.. code:: python

    AnySearch(pattern: Union[AnyStr, re.Pattern[AnyStr]], /)

A matcher that matches any string ``s`` for which ``re.search(pattern, s)``
succeeds

.. code:: python

    AnySubstr(s: AnyStr, /)

A matcher that matches any substring of ``s``

.. code:: python

    AnyWithAttrs(mapping: Mapping, /)

A matcher that matches any object ``obj`` such that ``getattr(obj, k) == v``
for all ``k,v`` in ``mapping.items()``.

The values (but not the keys) of ``mapping`` can be ``anys`` matchers.

.. code:: python

    AnyWithEntries(mapping: Mapping, /)

A matcher that matches any object ``obj`` such that ``obj[k] == v`` for all
``k,v`` in ``mapping.items()``.

The values (but not the keys) of ``mapping`` can be ``anys`` matchers.

.. code:: python

    Maybe(arg: Any, /)

A matcher that matches ``None`` and any value that equals or matches ``arg``
(which can be an ``anys`` matcher)

.. code:: python

    Not(arg: Any, /)

A matcher that matches anything that does not equal or match ``arg`` (which can
be an ``anys`` matcher)

Constants
---------

The following constants match values of the given type:

- ``ANY_BOOL``
- ``ANY_BYTES``
- ``ANY_COMPLEX``
- ``ANY_DATE`` — Matches ``date`` instances.  You may not be aware, but
  ``datetime`` is a subclass of ``date``, and so this also matches
  ``datetime``\s.  If you only want to match actual ``date``\s, use
  ``ANY_STRICT_DATE``.
- ``ANY_DATETIME``
- ``ANY_DICT``
- ``ANY_FLOAT``
- ``ANY_INT``
- ``ANY_ITERABLE``
- ``ANY_ITERATOR``
- ``ANY_LIST``
- ``ANY_MAPPING``
- ``ANY_NUMBER``
- ``ANY_SEQUENCE``
- ``ANY_SET``
- ``ANY_STR``
- ``ANY_STRICT_DATE`` — Matches any instance of ``date`` that is not an
  instance of ``datetime``
- ``ANY_TUPLE``

The following constants match `aware or naïve`__ ``datetime`` or ``time``
values:

__ https://docs.python.org/3/library/datetime.html#aware-and-naive-objects

- ``ANY_AWARE_DATETIME``
- ``ANY_AWARE_TIME``
- ``ANY_NAIVE_DATETIME``
- ``ANY_NAIVE_TIME``

The following constants match ISO 8601-style date, time, & datetime strings.
"Aware" matchers require timezone information, while "naïve" matchers forbid
it.

- ``ANY_AWARE_DATETIME_STR``
- ``ANY_AWARE_TIME_STR``
- ``ANY_DATETIME_STR``
- ``ANY_DATE_STR``
- ``ANY_NAIVE_DATETIME_STR``
- ``ANY_NAIVE_TIME_STR``
- ``ANY_TIME_STR``

Other constants:

- ``ANY_FALSY`` — Matches anything considered false
- ``ANY_TRUTHY`` — Matches anything considered true

Note: If you're after a matcher that matches absolutely everything, Python
already provides that as the `unittest.mock.ANY`__ constant.

__ https://docs.python.org/3/library/unittest.mock.html#any

Caveat: Custom Classes
======================

When a well-behaved class defines an ``__eq__`` method, it will only test
against values of the same class, returning ``NotImplemented`` for other types,
[1]_ which signals Python to evaluate ``x == y`` by instead calling ``y``'s
``__eq__`` method.  Thus, when comparing an ``anys`` matcher against an
instance of a well-behaved class, the matcher can be on either the left or the
right of the ``==``.  All of the classes in the Python standard library are
well-behaved, as are classes that don't define ``__eq__`` methods, but some
custom classes in third-party code are not well-behaved.  In order to
successfully compare an ``anys`` matcher against an ill-behaved class, the
matcher must be on the **left** side of the ``==`` operator; if it is on the
right, only the custom class's ``__eq__`` method will be consulted, which
usually means that the comparison will always evaluate to false.

.. [1] In order to work their magic, ``anys`` matchers do not follow this rule,
       and so they are not well-behaved.  "Do as I say, not as I do," as they
       say.

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/jwodder/anys",
    "name": "anys",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.7",
    "maintainer_email": "",
    "keywords": "comparison,matchers,pytest,testing,unit test",
    "author": "John Thorvald Wodder II",
    "author_email": "anys@varonathe.org",
    "download_url": "https://files.pythonhosted.org/packages/e6/44/3dae9eeb1637c3b0bd1136f0e4627d2a9384ad5d3325f3574c5e380a0699/anys-0.3.0.tar.gz",
    "platform": null,
    "description": ".. image:: http://www.repostatus.org/badges/latest/active.svg\n    :target: http://www.repostatus.org/#active\n    :alt: Project Status: Active \u2014 The project has reached a stable, usable\n          state and is being actively developed.\n\n.. image:: https://github.com/jwodder/anys/workflows/Test/badge.svg?branch=master\n    :target: https://github.com/jwodder/anys/actions?workflow=Test\n    :alt: CI Status\n\n.. image:: https://codecov.io/gh/jwodder/anys/branch/master/graph/badge.svg\n    :target: https://codecov.io/gh/jwodder/anys\n\n.. image:: https://img.shields.io/pypi/pyversions/anys.svg\n    :target: https://pypi.org/project/anys/\n\n.. image:: https://img.shields.io/conda/vn/conda-forge/anys.svg\n    :target: https://anaconda.org/conda-forge/anys\n    :alt: Conda Version\n\n.. image:: https://img.shields.io/github/license/jwodder/anys.svg\n    :target: https://opensource.org/licenses/MIT\n    :alt: MIT License\n\n`GitHub <https://github.com/jwodder/anys>`_\n| `PyPI <https://pypi.org/project/anys/>`_\n| `Issues <https://github.com/jwodder/anys/issues>`_\n| `Changelog <https://github.com/jwodder/anys/blob/master/CHANGELOG.md>`_\n\n``anys`` provides matchers for pytest_-style assertions.  What's a \"matcher,\"\nyou say?  Well, say you're writing a unit test and you want to assert that a\ngiven object contains the correct values.  Normally, you'd just write:\n\n.. code:: python\n\n    assert foo == {\n        \"widgets\": 42,\n        \"name\": \"Alice\",\n        \"subfoo\": {\n            \"created_at\": \"2021-06-24T18:41:59Z\",\n            \"name\": \"Bob\",\n            \"widgets\": 23,\n        }\n    }\n\nBut wait!  What if the value of ``foo[\"subfoo\"][\"created_at\"]`` can't be\ndetermined in advance, but you still need to check that it's a valid timestamp?\nYou'd have to compare everything in ``foo`` other than that one field to the\nexpected values and then separately check the timestamp field for validity.\nThis is where matchers come in: they're magic objects that compare equal to any\n& all values that meet given criteria.  For the case above, ``anys`` allows you\nto just write:\n\n.. code:: python\n\n    from anys import ANY_DATETIME_STR\n\n    assert foo == {\n        \"widgets\": 42,\n        \"name\": \"Alice\",\n        \"subfoo\": {\n            \"created_at\": ANY_DATETIME_STR,\n            \"name\": \"Bob\",\n            \"widgets\": 23,\n        }\n    }\n\nand the assertion will do what you mean.\n\n.. _pytest: https://docs.pytest.org\n\nInstallation\n============\n``anys`` requires Python 3.7 or higher.  Just use `pip <https://pip.pypa.io>`_\nfor Python 3 (You have pip, right?) to install it::\n\n    python3 -m pip install anys\n\n\nAPI\n===\n\n``anys`` provides the following classes & constants for matching against values\nmeeting certain criteria.  Matching is performed by comparing a value against\nan ``anys`` matcher with ``==``, either directly or as a result of comparing\ntwo larger structures with ``==``.\n\nIf a comparison raises a ``TypeError`` or ``ValueError`` (say, because you\nevaluated ``42 == AnyMatch(r'\\d+')``, which tries to match a regex against an\ninteger), the exception is suppressed, and the comparison evaluates to\n``False``; all other exceptions are propagated out.\n\n``anys`` matchers can be combined using the ``&`` operator to produce new\nmatchers that require both operands to succeed; for example, ``AnyGT(23) &\nAnyLT(42)`` will match any number between 23 and 42, exclusive, and nothing\nelse.\n\n``anys`` matchers can be combined using the ``|`` operator to produce new\nmatchers that require at least one of the operands to succeed; for example,\n``ANY_INT | ANY_STR`` will match any value that is an ``int`` or a ``str``.\n\nClasses\n-------\n\nNote that, unless stated otherwise, ``anys`` class constructors cannot take\n``anys`` matchers as arguments.\n\n.. code:: python\n\n    AnyContains(key: Any, /)\n\nA matcher that matches any value for which ``key in value`` is true.  If\n``key`` is an ``anys`` matcher, ``value == AnyContains(key)`` will instead be\nevaluated by iterating through the elements of ``value`` and checking whether\nany match ``key``.\n\n.. code:: python\n\n    AnyFullmatch(pattern: Union[AnyStr, re.Pattern[AnyStr]], /)\n\nA matcher that matches any string ``s`` for which ``re.fullmatch(pattern, s)``\nsucceeds\n\n.. code:: python\n\n    AnyFunc(func: Callable, /)\n\nA matcher that matches any value ``x`` for which ``func(x)`` is true.  If\n``func(x)`` raises a ``TypeError`` or ``ValueError``, it will be suppressed,\nand ``x == AnyFunc(func)`` will evaluate to ``False``.  All other exceptions\nare propagated out.\n\n.. code:: python\n\n    AnyGE(bound: Any, /)\n\nA matcher that matches any value greater than or equal to ``bound``\n\n.. code:: python\n\n    AnyGT(bound: Any, /)\n\nA matcher that matches any value greater than ``bound``\n\n.. code:: python\n\n    AnyIn(iterable: Iterable, /)\n\nA matcher that matches any value that equals or matches an element of\n``iterable`` (which may contain ``anys`` matchers).  Note that, if ``iterable``\nis a string, only individual characters in the string will match; to match\nsubstrings, use ``AnySubstr()`` instead.\n\n.. code:: python\n\n    AnyInstance(classinfo, /)\n\nA matcher that matches any value that is an instance of ``classinfo``.\n``classinfo`` can be either a type or a tuple of types (or, starting in Python\n3.10, a ``Union`` of types).\n\nA number of pre-composed ``AnyInstance()`` values are provided as constants for\nyour convenience; see \"Constants_\" below.\n\n.. code:: python\n\n    AnyLE(bound: Any, /)\n\nA matcher that matches any value less than or equal to ``bound``\n\n.. code:: python\n\n    AnyLT(bound: Any, /)\n\nA matcher that matches any value less than ``bound``\n\n.. code:: python\n\n    AnyMatch(pattern: Union[AnyStr, re.Pattern[AnyStr]], /)\n\nA matcher that matches any string ``s`` for which ``re.match(pattern, s)``\nsucceeds\n\n.. code:: python\n\n    AnySearch(pattern: Union[AnyStr, re.Pattern[AnyStr]], /)\n\nA matcher that matches any string ``s`` for which ``re.search(pattern, s)``\nsucceeds\n\n.. code:: python\n\n    AnySubstr(s: AnyStr, /)\n\nA matcher that matches any substring of ``s``\n\n.. code:: python\n\n    AnyWithAttrs(mapping: Mapping, /)\n\nA matcher that matches any object ``obj`` such that ``getattr(obj, k) == v``\nfor all ``k,v`` in ``mapping.items()``.\n\nThe values (but not the keys) of ``mapping`` can be ``anys`` matchers.\n\n.. code:: python\n\n    AnyWithEntries(mapping: Mapping, /)\n\nA matcher that matches any object ``obj`` such that ``obj[k] == v`` for all\n``k,v`` in ``mapping.items()``.\n\nThe values (but not the keys) of ``mapping`` can be ``anys`` matchers.\n\n.. code:: python\n\n    Maybe(arg: Any, /)\n\nA matcher that matches ``None`` and any value that equals or matches ``arg``\n(which can be an ``anys`` matcher)\n\n.. code:: python\n\n    Not(arg: Any, /)\n\nA matcher that matches anything that does not equal or match ``arg`` (which can\nbe an ``anys`` matcher)\n\nConstants\n---------\n\nThe following constants match values of the given type:\n\n- ``ANY_BOOL``\n- ``ANY_BYTES``\n- ``ANY_COMPLEX``\n- ``ANY_DATE`` \u2014 Matches ``date`` instances.  You may not be aware, but\n  ``datetime`` is a subclass of ``date``, and so this also matches\n  ``datetime``\\s.  If you only want to match actual ``date``\\s, use\n  ``ANY_STRICT_DATE``.\n- ``ANY_DATETIME``\n- ``ANY_DICT``\n- ``ANY_FLOAT``\n- ``ANY_INT``\n- ``ANY_ITERABLE``\n- ``ANY_ITERATOR``\n- ``ANY_LIST``\n- ``ANY_MAPPING``\n- ``ANY_NUMBER``\n- ``ANY_SEQUENCE``\n- ``ANY_SET``\n- ``ANY_STR``\n- ``ANY_STRICT_DATE`` \u2014 Matches any instance of ``date`` that is not an\n  instance of ``datetime``\n- ``ANY_TUPLE``\n\nThe following constants match `aware or na\u00efve`__ ``datetime`` or ``time``\nvalues:\n\n__ https://docs.python.org/3/library/datetime.html#aware-and-naive-objects\n\n- ``ANY_AWARE_DATETIME``\n- ``ANY_AWARE_TIME``\n- ``ANY_NAIVE_DATETIME``\n- ``ANY_NAIVE_TIME``\n\nThe following constants match ISO 8601-style date, time, & datetime strings.\n\"Aware\" matchers require timezone information, while \"na\u00efve\" matchers forbid\nit.\n\n- ``ANY_AWARE_DATETIME_STR``\n- ``ANY_AWARE_TIME_STR``\n- ``ANY_DATETIME_STR``\n- ``ANY_DATE_STR``\n- ``ANY_NAIVE_DATETIME_STR``\n- ``ANY_NAIVE_TIME_STR``\n- ``ANY_TIME_STR``\n\nOther constants:\n\n- ``ANY_FALSY`` \u2014 Matches anything considered false\n- ``ANY_TRUTHY`` \u2014 Matches anything considered true\n\nNote: If you're after a matcher that matches absolutely everything, Python\nalready provides that as the `unittest.mock.ANY`__ constant.\n\n__ https://docs.python.org/3/library/unittest.mock.html#any\n\nCaveat: Custom Classes\n======================\n\nWhen a well-behaved class defines an ``__eq__`` method, it will only test\nagainst values of the same class, returning ``NotImplemented`` for other types,\n[1]_ which signals Python to evaluate ``x == y`` by instead calling ``y``'s\n``__eq__`` method.  Thus, when comparing an ``anys`` matcher against an\ninstance of a well-behaved class, the matcher can be on either the left or the\nright of the ``==``.  All of the classes in the Python standard library are\nwell-behaved, as are classes that don't define ``__eq__`` methods, but some\ncustom classes in third-party code are not well-behaved.  In order to\nsuccessfully compare an ``anys`` matcher against an ill-behaved class, the\nmatcher must be on the **left** side of the ``==`` operator; if it is on the\nright, only the custom class's ``__eq__`` method will be consulted, which\nusually means that the comparison will always evaluate to false.\n\n.. [1] In order to work their magic, ``anys`` matchers do not follow this rule,\n       and so they are not well-behaved.  \"Do as I say, not as I do,\" as they\n       say.\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Matchers for pytest",
    "version": "0.3.0",
    "project_urls": {
        "Bug Tracker": "https://github.com/jwodder/anys/issues",
        "Homepage": "https://github.com/jwodder/anys",
        "Source Code": "https://github.com/jwodder/anys"
    },
    "split_keywords": [
        "comparison",
        "matchers",
        "pytest",
        "testing",
        "unit test"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "824c8a9bf37b3c051dd7d61945676211adbdc6f89f1c27b922e2d488fd32c7bb",
                "md5": "c9cc384fc3879c152e59d3483468aa98",
                "sha256": "ae4124d98ab0449a457d1563e86c9f9baa059707bf7bd65248d594b1fdc2a5c2"
            },
            "downloads": -1,
            "filename": "anys-0.3.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "c9cc384fc3879c152e59d3483468aa98",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.7",
            "size": 9073,
            "upload_time": "2023-09-12T19:57:47",
            "upload_time_iso_8601": "2023-09-12T19:57:47.361248Z",
            "url": "https://files.pythonhosted.org/packages/82/4c/8a9bf37b3c051dd7d61945676211adbdc6f89f1c27b922e2d488fd32c7bb/anys-0.3.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "e6443dae9eeb1637c3b0bd1136f0e4627d2a9384ad5d3325f3574c5e380a0699",
                "md5": "15b825d0461d08d643b0ce0776d061a3",
                "sha256": "56c04f76afe5379a5058aec067befa1fb93070d63cabbd342e04b3d5aff739f8"
            },
            "downloads": -1,
            "filename": "anys-0.3.0.tar.gz",
            "has_sig": false,
            "md5_digest": "15b825d0461d08d643b0ce0776d061a3",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.7",
            "size": 17429,
            "upload_time": "2023-09-12T19:57:49",
            "upload_time_iso_8601": "2023-09-12T19:57:49.025046Z",
            "url": "https://files.pythonhosted.org/packages/e6/44/3dae9eeb1637c3b0bd1136f0e4627d2a9384ad5d3325f3574c5e380a0699/anys-0.3.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2023-09-12 19:57:49",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "jwodder",
    "github_project": "anys",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "tox": true,
    "lcname": "anys"
}
        
Elapsed time: 0.23922s