mxutils

Contains helper functions and types that are written in Python and exclusive to the Python API.

Overview

Classes

mxutils.IdProvider(idType)

Generates int or maxon.Id identifiers when defining them manually is not desirable.

mxutils.LocalImportPath(path, autoReload)

Provides a context manger to import from packages relative to a file which do not lie in a module search path.

Functions

mxutils.CheckIterable(item[, tElement, …])

Checks item for being an iterable of a specific type, being composed of specific elements, or being of specific length.

mxutils.CheckType(item[, t, label])

Checks item for being of type t.

mxutils.ImportSymbols(path[, output])

Parses elements of all resource files contained in the given path into the global scope of the caller or into a dictionary.

Function Listing

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)
mxutils.CheckType(item: Any, t: Optional[Union[Type, tuple]] = None, label: str | None = None) → Any
Checks item for being of type t.

When the check fails, 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.
Parameters
  • item (typing.Any) – The item to be type checked.

  • t (typing.Type | tuple[typing.Type] | None (, optional)) – The type or types item is allowed to be. Passing None will only assert that item is not None. 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.

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

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

Returns

The value of item when the check succeeded.

Return type

typing.Any

Example

import c4d
import math

from mxutils import CheckType

MY_INT: int = 42

class Foo:
    SOME_INT: int = 42

    def __init__(self, x: int) -> None:
        # Asserts that `x` is not `None`.
        CheckType(x)

        # Asserts that `x` is not `None` and assigns its values to `self._x`.
        self._x: object = CheckType(x)

        # Asserts that `MY_INT` is of type `int` or `float` and assigns its int value to `self._y`.
        self._y: int = int(CheckType(MY_INT, (int, float)))


        # Symbols which are neither in the global nor local scope are not automatically
        # discoverable and error messages for such values will refer to the value with
        # 'unknown symbol'.

        # `Foo.SOME_INT` and `math.pi` are not part of the local or global scope. When error
        # messages should not refer to a generic 'Unknown Symbol', one must pass in a label.

        # CheckType(Foo.SOME_INT, bool)
        # TypeError: Expected value of type <class 'bool'> for 'Unknown Symbol'. Received: <class 'int'>

        CheckType(math.pi, int, 'math.pi')
        # TypeError: Expected value of type <class 'int'> for 'math.pi'. Received: <class 'float'>

        # `x`, `y`, and `MY_INT` are in the global/local scope and automatically discovered.
        y: float = 1.0

        CheckType(x, float) 
        # TypeError: Expected value of type <class 'float'> for 'x'. Received: <class 'int'>

        CheckType(y, int)
        # TypeError: Expected value of type <class 'int'> for 'y'. Received: <class 'float'>

        CheckType(MY_INT, bool)
        # TypeError: Expected value of type <class 'bool'> for 'MY_INT'. Received: <class 'int'>
mxutils.ImportSymbols(path: str, output: Type = None) → dict[str, float | int | str] | None

Parses elements of all resource files contained in the given path into the global scope of the caller or into a dictionary.

See also

Symbols Manual

Parameters
  • path (str) – The path within which header files should be discovered and parsed.

  • output (typing.Type) – If the parsed symbols should be placed in the global scope or be returned as a dictionary. Pass None to let them be placed in the global scope and dict to have the function return them as a dictionary. Defaults to None.

Returns

The parsed symbol value pairs as or nothing when the output was None

Return type

dict[str, float | int | str] | None

Example

The following header file c:\data\c4d_symbols.h,

enum
{
    ID_FOO = 1000,
    ID_BAR = 1001,
    _DUMMY_ELEMENT_
};

can be imported as follows:

import os
import mxutils

# Recursively parse header files in "c:\data" into this scope.
mxutils.ImportSymbols("c:\data")

# This would also work when only a singular file should be parsed.
mxutils.ImportSymbols("c:\data\c4d_symbols.h")

# The parsed symbols are now exposed in the global scope.
print(ID_FOO) # Will print 1000
print(ID_BAR) # Will print 1001

# Alternatively, symbols can also be parsed into a dictionary.
data: dict[str, int] = mxutils.ImportSymbols("c:\data\c4d_symbols.h", output=dict)

print(data["ID_FOO"]) # Will print 1000
print(data["ID_BAR"]) # Will print 1001