Skip to content

PyMeasures

Bases: PyObjects

Groups together multiple measures.

See PyObjects class for what more it can do. You can interact with PyMeasures straight from model. For ex: model.Measures. Or through individual tables model.Tables[TABLE_NAME].Measures. You can even filter down with .find(). For example find all measures with ratio in name. model.Measures.find('ratio').

Source code in pytabular/measure.py
class PyMeasures(PyObjects):
    """Groups together multiple measures.

    See `PyObjects` class for what more it can do.
    You can interact with `PyMeasures` straight from model. For ex: `model.Measures`.
    Or through individual tables `model.Tables[TABLE_NAME].Measures`.
    You can even filter down with `.find()`.
    For example find all measures with `ratio` in name.
    `model.Measures.find('ratio')`.
    """

    def __init__(self, objects, parent=None) -> None:
        """Extends init from `PyObjects`."""
        super().__init__(objects, parent)

    def __call__(self, *args, **kwargs):
        """Made `PyMeasures` just sends args through to `add_measure`."""
        return self.add_measure(*args, **kwargs)

    def add_measure(
        self, name: str, expression: str, auto_save: bool = True, **kwargs
    ) -> PyMeasure:
        """Add or replace measures from `PyMeasures` class.

        Required is just `name` and `expression`.
        But you can pass through any properties you wish to update as a kwarg.
        This method is also used when calling the class,
        so you can create a new measure that way.
        kwargs will be set via the `settr` built in function.
        Anything in the .Net Measures properties should be viable.
        [Measure Class](https://learn.microsoft.com/en-us/dotnet/api/microsoft.analysisservices.measure?#properties) # noqa: E501

        Example:
            ```
            expr = "SUM('Orders'[Amount])"
            model.Measures.add_measure("Orders Total", expr)
            ```

            ```
            expr = "SUM('Orders'[Amount])"
            model.Measures.add_measure("Orders Total", expr, Folder = 'Measures')
            ```

            ```
            expr = "SUM('Orders'[Amount])"
            model.Tables['Sales'].Measures('Total Sales', expr, Folder = 'Measures')
            ```

        Args:
            name (str): Name of the measure. Brackets ARE NOT required.
            expression (str): DAX expression for the measure.
            auto_save (bool, optional): Automatically save changes after measure creations.
                Defaults to `True`
        """
        if isinstance(self.parent._object, Table):
            table = self.parent
            model = self.parent.Model
        else:
            table = self.parent.Tables._first_visible_object()
            model = self.parent

        logger.debug(f"Creating measure in {table.Name}")

        new = True

        try:
            logger.debug(f"Measure {name} exists... Overwriting...")
            new_measure = self.parent.Measures[name]._object
            new = False
        except IndexError:
            logger.debug(f"Creating new measure {name}")
            new_measure = Measure()

        new_measure.set_Name(name)
        new_measure.set_Expression(expression)

        for key, value in kwargs.items():
            logger.debug(f"Setting '{key}'='{value}' for {new_measure.Name}")
            setattr(new_measure, key, value)

        if new:
            measures = table.get_Measures()
            measures.Add(new_measure)
        if auto_save:
            model.save_changes()
            return model.Measures[new_measure.Name]
        else:
            return True

__init__(objects, parent=None)

Extends init from PyObjects.

Source code in pytabular/measure.py
def __init__(self, objects, parent=None) -> None:
    """Extends init from `PyObjects`."""
    super().__init__(objects, parent)

__call__(*args, **kwargs)

Made PyMeasures just sends args through to add_measure.

Source code in pytabular/measure.py
def __call__(self, *args, **kwargs):
    """Made `PyMeasures` just sends args through to `add_measure`."""
    return self.add_measure(*args, **kwargs)

add_measure(name, expression, auto_save=True, **kwargs)

Add or replace measures from PyMeasures class.

Required is just name and expression. But you can pass through any properties you wish to update as a kwarg. This method is also used when calling the class, so you can create a new measure that way. kwargs will be set via the settr built in function. Anything in the .Net Measures properties should be viable. Measure Class # noqa: E501

Example
expr = "SUM('Orders'[Amount])"
model.Measures.add_measure("Orders Total", expr)
expr = "SUM('Orders'[Amount])"
model.Measures.add_measure("Orders Total", expr, Folder = 'Measures')
expr = "SUM('Orders'[Amount])"
model.Tables['Sales'].Measures('Total Sales', expr, Folder = 'Measures')

Parameters:

Name Type Description Default
name str

Name of the measure. Brackets ARE NOT required.

required
expression str

DAX expression for the measure.

required
auto_save bool

Automatically save changes after measure creations. Defaults to True

True
Source code in pytabular/measure.py
def add_measure(
    self, name: str, expression: str, auto_save: bool = True, **kwargs
) -> PyMeasure:
    """Add or replace measures from `PyMeasures` class.

    Required is just `name` and `expression`.
    But you can pass through any properties you wish to update as a kwarg.
    This method is also used when calling the class,
    so you can create a new measure that way.
    kwargs will be set via the `settr` built in function.
    Anything in the .Net Measures properties should be viable.
    [Measure Class](https://learn.microsoft.com/en-us/dotnet/api/microsoft.analysisservices.measure?#properties) # noqa: E501

    Example:
        ```
        expr = "SUM('Orders'[Amount])"
        model.Measures.add_measure("Orders Total", expr)
        ```

        ```
        expr = "SUM('Orders'[Amount])"
        model.Measures.add_measure("Orders Total", expr, Folder = 'Measures')
        ```

        ```
        expr = "SUM('Orders'[Amount])"
        model.Tables['Sales'].Measures('Total Sales', expr, Folder = 'Measures')
        ```

    Args:
        name (str): Name of the measure. Brackets ARE NOT required.
        expression (str): DAX expression for the measure.
        auto_save (bool, optional): Automatically save changes after measure creations.
            Defaults to `True`
    """
    if isinstance(self.parent._object, Table):
        table = self.parent
        model = self.parent.Model
    else:
        table = self.parent.Tables._first_visible_object()
        model = self.parent

    logger.debug(f"Creating measure in {table.Name}")

    new = True

    try:
        logger.debug(f"Measure {name} exists... Overwriting...")
        new_measure = self.parent.Measures[name]._object
        new = False
    except IndexError:
        logger.debug(f"Creating new measure {name}")
        new_measure = Measure()

    new_measure.set_Name(name)
    new_measure.set_Expression(expression)

    for key, value in kwargs.items():
        logger.debug(f"Setting '{key}'='{value}' for {new_measure.Name}")
        setattr(new_measure, key, value)

    if new:
        measures = table.get_Measures()
        measures.Add(new_measure)
    if auto_save:
        model.save_changes()
        return model.Measures[new_measure.Name]
    else:
        return True

The main parent class for grouping your (Tables, Columns, Measures, Partitions, etc.).

Notice the magic methods. __rich_repr__() starts the baseline for displaying your model. It uses the amazing rich python package and builds your display from the self._display. Still building out the magic methods to give PyObjects more flexibility.

Source code in pytabular/object.py
class PyObjects:
    """The main parent class for grouping your (Tables, Columns, Measures, Partitions, etc.).

    Notice the magic methods. `__rich_repr__()` starts the baseline for displaying your model.
    It uses the amazing `rich` python package and
    builds your display from the `self._display`.
    Still building out the magic methods to give `PyObjects` more flexibility.
    """

    def __init__(self, objects: list[PyObject], parent=None) -> None:
        """Initialization of `PyObjects`.

        Takes the objects in something that is iterable.
        Then will build a default `rich` table display.

        Args:
            objects(list[PyObject]): .Net objects.
            parent: Parent Object. Defaults to `None`.
        """
        self._objects = objects
        self.parent = parent
        self._display = Table(title=str(self.__class__.mro()[0]))
        for index, obj in enumerate(self._objects):
            self._display.add_row(str(index), obj.Name)

    def __rich_repr__(self) -> str:
        """See [Rich Repr](https://rich.readthedocs.io/en/stable/pretty.html#rich-repr-protocol)."""
        Console().print(self._display)

    def __getitem__(self, object):
        """Get item from `PyObjects`.

        Checks if item is str or int.
        If string will iterate through and try to find matching name.
        Otherwise, will call into `self._objects[int]` to retrieve item.
        """
        if isinstance(object, str):
            return [pyobject for pyobject in self._objects if object == pyobject.Name][
                -1
            ]
        elif isinstance(object, slice):
            cls = type(self)
            return cls(self._objects[object])
        else:
            return self._objects[object]

    def __iter__(self):
        """Iterate through `PyObjects`."""
        yield from self._objects

    def __len__(self) -> int:
        """Get length of `PyObjects`.

        Returns:
            int: Number of PyObject in PyObjects
        """
        return len(self._objects)

    def __iadd__(self, obj):
        """Add a `PyObject` or `PyObjects` to your current `PyObjects` class.

        This is useful for building out a custom `PyObjects` class to work with.
        """
        if isinstance(obj, Iterable):
            self._objects.__iadd__(obj._objects)
        else:
            self._objects.__iadd__([obj])

        self.__init__(self._objects)
        return self

    def _first_visible_object(self):
        """Does what the method is called. Get's first `object.IsHidden is False`."""
        for object in self:
            if object.IsHidden is False:
                return object
        return None

    def find(self, object_str: str):
        """Finds any or all `PyObject` inside of `PyObjects` that match the `object_str`.

        It is case insensitive.

        Args:
            object_str (str): str to lookup in `PyObjects`

        Returns:
            PyObjects (object.PyObjects): Returns a `PyObjects` class with all `PyObject`
                where the `PyObject.Name` matches `object_str`.
        """
        items = [
            object
            for object in self._objects
            if object_str.lower() in object.Name.lower()
        ]
        return self.__class__.mro()[0](items)

    def get(self, object_str: str, alt_result: str = "") -> str:
        """Gets the object based on str.

        If the object isnt found, then an alternate result
        can be supplied as an argument.

        Args:
            object_str (str): str to lookup object
            alt_result (str): str to return when value isn't found.

        Returns:
            str: Result of the lookup, or the alternate result.
        """
        try:
            return self.__getitem__(object_str)
        except Exception as e:
            Console().print(e)

        return alt_result

find(object_str)

Finds any or all PyObject inside of PyObjects that match the object_str.

It is case insensitive.

Parameters:

Name Type Description Default
object_str str

str to lookup in PyObjects

required

Returns:

Name Type Description
PyObjects PyObjects

Returns a PyObjects class with all PyObject where the PyObject.Name matches object_str.

Source code in pytabular/object.py
def find(self, object_str: str):
    """Finds any or all `PyObject` inside of `PyObjects` that match the `object_str`.

    It is case insensitive.

    Args:
        object_str (str): str to lookup in `PyObjects`

    Returns:
        PyObjects (object.PyObjects): Returns a `PyObjects` class with all `PyObject`
            where the `PyObject.Name` matches `object_str`.
    """
    items = [
        object
        for object in self._objects
        if object_str.lower() in object.Name.lower()
    ]
    return self.__class__.mro()[0](items)