Handy wrappers for advanced object comparing and matching.
Project description
compr - flexible comparing and matching
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 copmr
>>> 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. Searhing 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% precision"""
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
Built Distribution
File details
Details for the file compr-0.1.4.tar.gz
.
File metadata
- Download URL: compr-0.1.4.tar.gz
- Upload date:
- Size: 5.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/4.0.1 CPython/3.9.13
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | f455476a22cec17eb9c2f356f7a86e3089de257d0b096e5879e4aeaa35355803 |
|
MD5 | 4efaf0ae2487a83af198ca704ad77409 |
|
BLAKE2b-256 | 415a73321bb94e958c9304e7fafaff404c17e1eb15adfe0262e7406a049371a2 |
File details
Details for the file compr-0.1.4-py3-none-any.whl
.
File metadata
- Download URL: compr-0.1.4-py3-none-any.whl
- Upload date:
- Size: 5.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/4.0.1 CPython/3.9.13
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | de4261e31b981bd868602b01a132f0c223d404ac1dd1362615e0808b2d63087a |
|
MD5 | c14efacf61a4301a624cb52ffcbb486e |
|
BLAKE2b-256 | 4bf907eb8c00900e3f8f69e491662caed42f913d8bc7663a348969500019e789 |