mxutils.CheckIterable

mxutils.CheckIterable(item: Any, tElement: Optional[Union[Type, tuple]] = None, tIterable: Optional[Union[Type, tuple]] = None, label: str | None = None, minCount: int | None = None, maxCount: int | None = None) → Any
Checks item for being an iterable of a specific type, being composed of specific elements, or being of specific length.

When any of the checks fail, an error is being raised. If not, item is being passed through. The function can automatically discover the symbol of item when it is defined in the local or global scope of the function call. While all types of iterables are supported, it is only dict and c4d.BaseContainer which are supported in their specific item, i.e., pairs, decomposition. All other iterables are treated as iterables of singular elements as for example lists or tuples.

When possible, it is advantageous to replace iterable type checks with type conversions due to the computationally expensive nature of checking every item in an iterable. The expensive part of this call is passing not None for tElement, i.e., checking the type of every element in item (when item is very large), all other checks are negligible in cost.
Parameters
  • item (typing.Any) – The item to be type checked.

  • tElement (typing.Type | tuple[typing.Type] | None (, optional)) – The type or types of elements the iterable item is allowed to have. Passing None will will accept any kind of element type. Defaults to None.

  • tIterable (typing.Type | tuple[typing.Type] | None (, optional)) – The type or types the iterable item is allowed to be. Passing None will accept any kind of iterable. Defaults to None.

  • label (str | None (, optional)) – The label with which to refer to item in exceptions. Passing None will result in an attempt to infer the symbol. Symbol discovery only works for values in the local or global scope. Defaults to None.

  • minCount (int | None (, optional)) – The minimum length for the iterable item. Passing None will accept any iterable that is smaller than or equal to maxCount. Value must be smaller than or equal to maxCount Defaults to None.

  • maxCount (int | None (, optional)) – The maximum length for the iterable item. Passing None will accept any iterable that is greater than or equal to minCount. Value must be larger or equal to minCount Defaults to None.

Raises
  • TypeError – When item is failing the type tests.

  • ValueError – When minCount is larger than maxCount.

  • RuntimeError – When any of the length checks fail.

  • RuntimeError – When accessing a stack trace or reading an executed file failed on a symbol discovery attempt.

Returns

The value of item when the checks succeeded.

Return type

typing.Any

Example

import c4d

from mxutils import CheckIterable

# Checks if `data` is composed of strings only; this will fail as there is an `int` in it.
data: list[str|int] = ["1", "2", "3", 4, "5"]
data = CheckIterable(data, str)

# But the prior case is also an example for where element type assertions are unnecessary. 
# As we can enforce the type of items in `data` with `str()`.
item: str = str(data[3])

# The common use case is checking type composition assumptions of function arguments passed 
# by unsafe sources, in order to avoid carrying out expensive computations which fail in a 
# very late stage due to encountering a malformed item in the input data.
class Foo:
    def __init__(self, nodes: list[c4d.BaseObject | c4d.BaseTag]) -> None:
        # Assure that _nodes will be a list of tags and objects.
        self._nodes = CheckIterable(nodes, (c4d.BaseObject, c4d.BaseTag), list)

# Tested can also be the length of the iterable.

# Assures that `data` has at least the length of one. No specific type checks are performed 
# in this case, but `data` is still being tested for being an iterable. This is a very low
# cost call, no matter how large data is, as the first argument `tElement` has is `None` and
# `data` is therefore never traversed as elements of any type are accepted.
data = CheckIterable(data, minCount=1)

# Assures that `data` is not longer than five elements.
data = CheckIterable(data, maxCount=5)

# Assures that the length of `data` lies in the interval [1, 5].
data = CheckIterable(data, minCount=1, maxCount=5)

# The function will always test if ``item`` is an iterable, even when both type tests are
# set to None. So, this will raise an error as `int` does not implement `__iter__`
CheckIterable(1)

# but this will not, as `c4d.BaseContainer` does implement `__iter__`:
data: c4d.BaseContainer = c4d.BaseContainer()
CheckIterable(data)

# All forms of iterable are supported, but only `c4d.BaseContainer` and `dict` are handled
# manually to type test values in key, value pairs instead of testing the type of the pair.

data[0] = "Hello world"
data[1] = True
# Will test data for being of type BaseContainer, holding only values of type str or bool.
CheckIterable(data, (str, bool), c4d.BaseContainer)

# The same thing for dict, key type testing is not supported.
data = {1: "Hello World", "my_key": True}
CheckIterable(data, (str, bool), dict)

# Just as for CheckType(), automatic symbol discovery is only possible for values in the
# local or global scope. So, when testing `sys.path`, one should pass a label, as errors
# will otherwise refer to an 'unknown symbol'.

import sys
# Assures that sys.path is a list of strings with at least one element.
paths = CheckIterable(sys.path, str, list, "sys.path", 1)