Skip to main content

Handy wrappers for advanced object comparing and matching.

Project description

compr - flexible comparing and matching

PyPI version Python versions License: MIT

Rationale

compr is a collection of wrappers (comparators) that enable flexible and complex comparing scenarios via simple code.

Comparators bind value(s) AND compare logic into single object. Quite similar to what pytest.approx(...) does.

Comparators redefine __eq__. The goal: exploit == to simplify underlying code (mainly by getting rid of extra if-else's later). Works well with assertions, filters, dispatchers etc.

Human readable __repr__ is also available!

Installation

pip install compr

List available comparator functions

>>> import compr
>>> list(compr._COMPARATORS)

Usage

We create comparators via functions in compr module:

>>> import compr
>>> contains_5 = compr.contains(5)  # matches iterable with 5 as one of elements
>>> [3, 4, 6,] == contains_5
False
>>> range(10) == contains_5
True

The real profit comes by using comparators as arguments. So underlying handlers can stay simple (check only ==). While flexibility is achieved by providing different comparators when needed.

Please see examples (also the one with kwargs2compr demonstrates in-depth usage scenario).

NOTE: These examples are intentionally synthetic for better illustration.

Example 0. Basic assert (note the assert message):

# Assert actual value is greater than 5
>>> import compr
>>> expected = compr.gt(5)  # gt stands for greater than as in __gt__
>>> actual = 3
>>> assert expected == actual, f'{actual=} does not match {expected=}'
...
AssertionError: actual=3 does not match expected=gt(5)

Example 1. Searching list by condition:

# Find index of the first word starting with 't'
>>> from compr import startswith
>>> words = 'one', 'two', 'three', 'four'
>>> words.index(startswith('t'))
1

Example 2: Validate HTTP response object attributes

Imagine requests-like response object:

>>> dir(response)
['status', 'headers', 'body', 'body_len'] # int, dict, str, int respectively

Without compr validator with parateters may look like:

>>> def validate(response, **kwargs):
>>>     """
>>>     Check if all given kwargs equal to response attributes
>>>     """
>>>     for k, v in kwargs.items():
>>>         if getattr(response, k) == v:
>>>             continue
>>>         else:
>>>             raise AssertionError(k, v)

Usage (check if status is 200 and body is 'asdf'):

>>> validate(response, status=200, body='asdf')

Now let's expand our requirements. We need to check if:

  • body_len is between 300 and 400 bytes (inclusive)
  • status code is < 206.

Pushing compare logic into validator above will eventually overwhelm it with complex code, extra arguments or even magic.

Instead we may keep method above unchanged and use comparators:

>>> from compr import lt, within  # less-than, fits-range
>>> validate(response, status=lt(206), body_len=within(300, 400))

Now when validate tries to == status, it will actually check if its less-than 206. Similarly with == body_len, it will check if it fits into range [300..400].

Profit!

Few more steps can make things prettier (introducing kwargs2compr):

>>> from compr import kwargs2compr
>>>
>>> def validate(response, **kwargs):
>>>     for k, v in kwargs2compr(kwargs):  # arg name parsing kicks in here
>>>     # allow kwargs to be interpreted as
>>>     # <attribute-name>_<comparator-func-name>=<comparator-func-argument>
>>>         if getattr(response, k) == v:
>>>             continue
>>>         else:
>>>             raise AssertionError(k, v)

And usage becomes:

>>> validate(response, status_lt=206, body_len_within=[300, 400])

After more refactoring (see all_attrs docstring for help):

>>> from compr import all_attrs
>>>
>>> def validate(response, **kwargs):
>>>         if not all_attrs(response, kwargs2compr(kwargs)):
>>>             raise AssertionError(response, kwargs)

Example 3: Combining multiple comparators

2 special functions: match_all and match_any allow including other comparators to check against complex conditions

# Check if list shorter than 3 elements OR contains 1
>>> from compr import match_any, shorter_than, contains
>>> [2, 3] == match_any(shorter_than(3), contains(1))
True
>>> [1, 2, 3, 5] == match_any(shorter_than(3), contains(1))
True
>>> [2, 3, 5, 6, 7] == match_any(shorter_than(3), contains(1))
False

Creating Your own comparator object:

@compr.comparator
def somewhat_equal(actual, expected):
    """Compare with 10% tolerance"""
    return 0.9 * expected < actual < 1.1 * expected

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

compr-0.1.5.tar.gz (4.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

compr-0.1.5-py3-none-any.whl (4.1 kB view details)

Uploaded Python 3

File details

Details for the file compr-0.1.5.tar.gz.

File metadata

  • Download URL: compr-0.1.5.tar.gz
  • Upload date:
  • Size: 4.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for compr-0.1.5.tar.gz
Algorithm Hash digest
SHA256 d1e04747f5f034ec43201761d02ede16a6bdf2dc8cc077e17d980a6fd4c0094b
MD5 e44e19d055748dee2c480f0526d2ea92
BLAKE2b-256 40e5e21fe8566149df6eeee794e7d4410f720e9f443c239e9321c1919df818bb

See more details on using hashes here.

Provenance

The following attestation bundles were made for compr-0.1.5.tar.gz:

Publisher: publish-to-pypi.yml on ykvch/compr

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file compr-0.1.5-py3-none-any.whl.

File metadata

  • Download URL: compr-0.1.5-py3-none-any.whl
  • Upload date:
  • Size: 4.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for compr-0.1.5-py3-none-any.whl
Algorithm Hash digest
SHA256 269aadb3e61068403f7d708a5c9b2e58f726a16d914c7d75fb6cf0e70bd38c31
MD5 cbad6ce8b3c6c52872e53ab229bdb75c
BLAKE2b-256 fcdcd964393f06c6d312f2c01275dbffb4c6b786c1215485074df6b6e4f145ed

See more details on using hashes here.

Provenance

The following attestation bundles were made for compr-0.1.5-py3-none-any.whl:

Publisher: publish-to-pypi.yml on ykvch/compr

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page