Library for writing composable predicates
Project description
cmplib
cmplib is a library used for writing composable matchers for your tests, validators etc. cmplib's matchers are compared for equality (or inequality) against your values.
Each matcher provides a human-readable string representation (implemented
by __repr__
dunder method).
List of Matchers
Eq, Ne, Gt, Ge, Lt, Le, Not
Check whether a checked-against value is equal to value stored in matcher,
not equal, greater than, greater or equal, less than or less or equal. Using
of Not
negates the meaning of stored sub-matcher or value.
assert "abc" == Eq("abc")
assert "abc" == Eq(And(Contains("b"), Len(3)))
assert 1 != Eq(2)
assert 1 == Gt(0)
assert False == Not(Truish())
assert True != Not(Truish())
assert 1 == Not(Gt(1))
assert 1 == Not(2)
Is
Check whether a value is the same object as the one stored in a matcher.
obj = object()
assert obj == Is(obj)
assert list() != Is(list())
IsNone
Check whether a value is None
.
assert None == IsNone()
assert 0 == Not(IsNone())
assert [] != IsNone()
And, Or
Check whether all sub-matchers match the value (for All
), or any of them
(for Or
).
l = [1, 2, 3]
assert l == And(Contains(1), Contains(2))
assert l != And(Contains(1), Contains(2), Contains(3), Contains(4))
assert l == Or(Contains(5), Contains(2))
assert l != Or(Contains(5), Contains(6), Contains(7), Contains(8))
Instead of passing matchers directly to And
or Or
, they can be composed
with bitwise and operator (&
) and bitwise or operator (|
).
l = [1, 2, 3]
assert l == Contains(1) & Contains(2)
assert l == Contains(5) | Contains(2)
Truish
Check whether value casts to True
boolean, as in bool(value)
.
assert True == Truish()
assert 1 == Truish()
assert "foo" == Truish()
assert False != Truish()
Len
Check whether container's size matches the matcher.
assert [] == Len(0)
assert [1, 2, 3, "aaa"] == Len(And(Gt(0), Lt(5)))
assert [1, 2, 3, "aaa"] != Len(And(Gt(0), Lt(4)))
IsEmpty
Check whetner container is empty (it's size is 0).
assert [] == IsEmpty()
assert "" == IsEmpty()
assert dict() == IsEmpty()
assert [1] != IsEmpty()
assert "a" != IsEmpty()
Each
Check whether each element of iterable matches the stored sub-matcher or value.
assert [1, 1, 1] == Each(1)
assert [1, 2, 3] == Each(Not(0))
Values
Check whether iterable contains at least 1 value which matches a stored value
or sub-matcher. Number of required matching values may be changed with
keyword-only argument at_least
.
Implementation detail: for checking whether a container contains a single
value, it's better to use Contains
which will perform better for
certain types of containers (like sets or dictionaries).
assert [0, 2, 3, 1, 2] == Values(1)
assert [] == Values(1, at_least=0)
assert [0, 2, 3, 1, 2] == Values(Gt(1), at_least=2)
assert [] != Values(1)
assert [0, 2, 3, 1, 2] != Values(1, at_least=2)
Contains
Check whether iterable contains a value which matches stored value or sub-matcher.
assert [1, 2, 3] == Contains(1)
assert [1, 2, 3] == Contains(Or("a", 2, "b"))
Unordered
Check whether iterable matches stored values or matchers. Each item must match exactly one matcher, but matching order doesn't matter.
assert [] == Unordered()
assert [1, 2, 3] == Unordered(3, 2, 1)
assert [1, 2, 3] == Unordered(Eq(3), Or(7, 2), 1)
assert [1, 2, 3] != Unordered("1", "2", "3")
To perform ordered search, simply construct an iterable of matchers.
assert [1, 2, 3] == [Eq(1), Or(7, 2), 3]
assert [1, 2, 3] != [Eq(3), Or(7, 2), 1]
KeyEq, AttrEq
For KeyEq
check whether a object stores a value under a key. Key can be
either a key in a dict-like object, or index in a list-like or tuple-like
objects.
d = {"foo": "bar", "baz": "blah"}
assert d == KeyEq("foo", "bar")
assert d == KeyEq("foo", Not(IsEmpty()))
assert d == KeyEq("foo", Not(Contains("h")) & Contains("b") & Contains("a"))
lst = ["foo", "bar"]
assert lst == KeyEq(1, "bar")
assert lst == KeyEq(-1, "bar")
For AttrEq
check whether an object stores a value under attribute.
@dataclass
class Foo:
foo: str = "bar"
baz: str = "blah"
o = Foo()
assert o == AttrEq("foo", "bar")
assert o != AttrEq("foo", "blah")
IsInstance
Check whether a value is an instance of a given type.
assert 1 == IsInstance(int)
assert datetime.now() == IsInstance(datetime)
Object, DictFields
Composes a matcher from a keyword-only arguments. Each of these arguments
must match a corresponding attribute (for Object
) or key (for DictFields
)
of checked item.
Object
additionally accepts a single optional, non-keyword argument, which
matches against a type of matched item.
These are convenience matchers. The same effect can be accomplished by using
bare AttrEq
and KeyEq
matchers.
@dataclass
class Foo:
foo: int = 1
bar: int = 2
baz: str = "s"
o: Optional["Foo"] = None
assert Foo() == Object(foo=1, bar=Ge(2))
assert Foo(o=Foo()) == Object(foo=1, o=Object(bar=2, o=None))
assert Foo() != Object(dict, foo=1)
assert Foo() != Object(nonexisting=1)
d = {"foo": 1, "bar": 2, "baz": "s", "o": {"foo": 1}}
assert d == DictFields(foo=1, bar=Ge(2))
assert d == DictFields(foo=1, o=DictFields(foo=1))
assert d != DictFields(foo=1, o=DictFields(foo=2))
assert d != DictFields(foo=1, o=DictFields(nonexisting=2))
Items
Composes a matcher from a index-matcher pairs. Each matcher must match a value in the compared container under the corresponding index.
lst = ["foo", "bar", "baz", "blah"]
assert lst == Items((0, "foo"), (-1, "blah"))
assert lst == Items((-1, "blah"), (0, "foo"))
assert lst != Items((0, "foo"), (-1, "blah2"))
assert lst != Items((0, "foo"), (100, "foo"))
Glob
Test whether a astring matches a given globbing (wildcard) expression.
Matching rules of fnmatch.fnmatchcase
apply.
assert "foo" == Glob("*")
assert ["foo", "bar"] == [Glob("f*"), Glob("b*")]
Glob
module supports the following wildcards (same as fnmatch
module):
*
: matches everything?
: matches any single character[seq]
: matches any character in seq[!seq]
: matches any character not in seq
It is possible to automatically convert checked values to string. This way non-string types which support such conversion can be matched as well.
from pathlib import Path
assert Path("/foo/bar") == Glob("/foo/*", coerce=True)
Matching is case-sensitive, but it can be changed to case-insensitive by
passing case=False
.
assert "Foo" == Glob("f*", case=False)
assert "Foo" != Glob("f*")
Re
Test whether a astring matches a given regular expression. Matching rules of
re.match
apply. Re
matcher doesn't store capture groups.
assert "foo bar baz" == Re("^foo.*z$")
assert ["foo", "bar"] == [Re("f.*"), Re("b.*")]
It is possible to automatically convert checked values to string. This way non-string types which support such conversion can be matched as well.
assert 11 == Re(r"\d+", coerce=True)
Re
matcher accepts the same flags as functions in ordinary re
module.
import re
assert "fOO bar baz\n" == Re("Foo.*", flags = re.IGNORECASE | re.DOTALL)
CanBeTimestamp
Test whether a value can be converted to a UNIX timestamp. UNIX timestamps are floating point numbers, so this means that any value which can be converted to the correct float are considered as such.
assert 0 == CanBeTimestamp()
assert "0" == CanBeTimestamp()
assert "0.123" == CanBeTimestamp()
assert datetime.now().timestamp() == CanBeTimestamp()
assert "" != CanBeTimestamp()
assert datetime.now() != CanBeTimestamp()
IsIsoDateTime
Check whether a value represents a valid datetime (either a date
or
datetime
object or is a value which follows ISO 8601 format and can be
converted to such value).
assert datetime.now() == IsIsoDateTime()
assert datetime.today() == IsIsoDateTime()
assert datetime.now().isoformat() == IsIsoDateTime()
assert datetime.today().isoformat() == IsIsoDateTime()
assert "2021-01-01" == IsIsoDateTime()
assert "2022-03" != IsIsoDateTime()
IsUnique
Caches values which were already checked against IsUnique
and matches them
as long as no such value was previously matched. It is possible to specify a
cache name which should hold values. Values are only compared against other
values stored in the same cache. It is possible to clear a particular cache
or all caches
assert 1 == IsUnique("cache-1")
assert 1 == IsUnique("cache-2")
assert 2 == IsUnique("cache-1")
assert 1 != IsUnique("cache-1")
IsUnique.clear("cache-1")
assert 1 == IsUnique("cache-1")
assert 1 != IsUnique("cache-2")
IsUnique.clear()
assert 1 == IsUnique("cache-1")
assert 1 == IsUnique("cache-2")
Creating IsUnique
without a cache name results in generating a new cache
for each instance created this way.
U = IsUnique()
assert 1 == U
assert 1 != U
assert 1 == IsUnique()
assert 1 == IsUnique()
assert [1, 2, 3, 4] == Each(IsUnique())
assert [1, 2, 3, 4, 1, 1, 7] != Each(IsUnique())
Fn
Matches true when a function called with a value passed as the first argument
returns True. When coerce
is set to True, stored function doesn't have to
return a boolean, but instead its return value is casted to boolean.
assert 1 == Fn(lambda x: x == 1)
assert "1" == Fn((lambda x: x), coerce=True)
SKIP
SKIP
is a sentinel which always matches true and can be used to mark not
interesing items.
assert [1, 2, 3] == [SKIP, SKIP, SKIP]
assert [1, 2, 3] == [1, SKIP, SKIP]
License
cmplib is licensed under the terms of LGPLv3.
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 cmplib-1.3.0.tar.gz
.
File metadata
- Download URL: cmplib-1.3.0.tar.gz
- Upload date:
- Size: 19.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/1.8.3 CPython/3.11.9 Linux/6.7.12-amd64
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | f90d89c351a7f2b8192a107287e3b36e9f4e4b7a49af4b1ce752e9b6234bf48e |
|
MD5 | bb6f47cc4e12a2b24a0b06936d6179ad |
|
BLAKE2b-256 | 991150fb26a3f8f32270ffe60cebfd32d1104e0cfa290279bf11599861094acb |
File details
Details for the file cmplib-1.3.0-py3-none-any.whl
.
File metadata
- Download URL: cmplib-1.3.0-py3-none-any.whl
- Upload date:
- Size: 21.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/1.8.3 CPython/3.11.9 Linux/6.7.12-amd64
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | a081b9d5ae96d6bccd5503103f1ded4d07003d077243a744f0c7f2a61c5f9baa |
|
MD5 | b92efc0ca59b866ad7c3b5b9166a577d |
|
BLAKE2b-256 | 1207182eeb7fdb7fc3a08fc6948a97d5ef0d6d68e6f1da8ecd3b21c0b579c1c6 |