Skip to main content

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

cmplib-1.3.1.tar.gz (20.7 kB view details)

Uploaded Source

Built Distribution

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

cmplib-1.3.1-py3-none-any.whl (9.7 kB view details)

Uploaded Python 3

File details

Details for the file cmplib-1.3.1.tar.gz.

File metadata

  • Download URL: cmplib-1.3.1.tar.gz
  • Upload date:
  • Size: 20.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.6.4

File hashes

Hashes for cmplib-1.3.1.tar.gz
Algorithm Hash digest
SHA256 1143ce6fea2939fa5952ae3fa0a4f82fcdbae2caefad7c7796b7040531016b4e
MD5 ccf4879e60192f3dd3708a3abcb7e479
BLAKE2b-256 1f91146bb40043a2711aaa29c9e55d9fffe154847abd8b6f300be7e313a238b9

See more details on using hashes here.

File details

Details for the file cmplib-1.3.1-py3-none-any.whl.

File metadata

  • Download URL: cmplib-1.3.1-py3-none-any.whl
  • Upload date:
  • Size: 9.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.6.4

File hashes

Hashes for cmplib-1.3.1-py3-none-any.whl
Algorithm Hash digest
SHA256 208f39e43c6c60b5cbfeceaba51ed670d54b25d152e318ffb1281cbaf37c324d
MD5 24098f665ffaa7c8a955ea508ed1c3b0
BLAKE2b-256 e2da28f199edd99a36db8186af1a85b7dc8592ea0ee6303b3ffdd49fecbf883a

See more details on using hashes here.

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