{"id":18084852,"url":"https://github.com/jwodder/anys","last_synced_at":"2025-08-21T10:31:01.380Z","repository":{"id":57410720,"uuid":"380064753","full_name":"jwodder/anys","owner":"jwodder","description":"Matchers for pytest","archived":false,"fork":false,"pushed_at":"2024-12-01T12:19:47.000Z","size":73,"stargazers_count":20,"open_issues_count":2,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-12-09T22:42:29.667Z","etag":null,"topics":["available-on-pypi","comparison","matchers","pytest","python","testing","unit-test"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jwodder.png","metadata":{"files":{"readme":"README.rst","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-06-24T22:29:14.000Z","updated_at":"2024-12-09T22:05:42.000Z","dependencies_parsed_at":"2023-01-31T02:00:57.616Z","dependency_job_id":"9e993652-8c8f-48f2-9725-a191771a0384","html_url":"https://github.com/jwodder/anys","commit_stats":{"total_commits":54,"total_committers":1,"mean_commits":54.0,"dds":0.0,"last_synced_commit":"04f4e122bb32b31180d94a3081cee406840f1cd4"},"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jwodder%2Fanys","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jwodder%2Fanys/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jwodder%2Fanys/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jwodder%2Fanys/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jwodder","download_url":"https://codeload.github.com/jwodder/anys/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":230507051,"owners_count":18236944,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["available-on-pypi","comparison","matchers","pytest","python","testing","unit-test"],"created_at":"2024-10-31T15:08:26.343Z","updated_at":"2024-12-19T22:07:59.652Z","avatar_url":"https://github.com/jwodder.png","language":"Python","readme":"|repostatus| |ci-status| |coverage| |pyversions| |conda| |license|\n\n.. |repostatus| image:: https://www.repostatus.org/badges/latest/active.svg\n    :target: https://www.repostatus.org/#active\n    :alt: Project Status: Active — The project has reached a stable, usable\n          state and is being actively developed.\n\n.. |ci-status| image:: https://github.com/jwodder/anys/actions/workflows/test.yml/badge.svg\n    :target: https://github.com/jwodder/anys/actions/workflows/test.yml\n    :alt: CI Status\n\n.. |coverage| image:: https://codecov.io/gh/jwodder/anys/branch/master/graph/badge.svg\n    :target: https://codecov.io/gh/jwodder/anys\n\n.. |pyversions| image:: https://img.shields.io/pypi/pyversions/anys.svg\n    :target: https://pypi.org/project/anys/\n\n.. |conda| 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.. |license| image:: https://img.shields.io/github/license/jwodder/anys.svg\n    :target: https://opensource.org/licenses/MIT\n    :alt: MIT License\n\n`GitHub \u003chttps://github.com/jwodder/anys\u003e`_\n| `PyPI \u003chttps://pypi.org/project/anys/\u003e`_\n| `Issues \u003chttps://github.com/jwodder/anys/issues\u003e`_\n| `Changelog \u003chttps://github.com/jwodder/anys/blob/master/CHANGELOG.md\u003e`_\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\u0026 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.8 or higher.  Just use `pip \u003chttps://pip.pypa.io\u003e`_\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 \u0026 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 ``\u0026`` operator to produce new\nmatchers that require both operands to succeed; for example, ``AnyGT(23) \u0026\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`` — 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`` — Matches any instance of ``date`` that is not an\n  instance of ``datetime``\n- ``ANY_TUPLE``\n\nThe following constants match `aware or naïve`__ ``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, \u0026 datetime strings.\n\"Aware\" matchers require timezone information, while \"naïve\" 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`` — Matches anything considered false\n- ``ANY_TRUTHY`` — 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","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjwodder%2Fanys","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjwodder%2Fanys","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjwodder%2Fanys/lists"}