Source code for pogona.properties

# Pogona
# Copyright (C) 2020 Data Communications and Networking (TKN), TU Berlin
#
# This file is part of Pogona.
#
# Pogona is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Pogona is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Pogona.  If not, see <https://www.gnu.org/licenses/>.

"""
Properties for Components.
Assign instances of these to class attributes.
The Component base class will automatically convert these to instance
variables in __init__.

This solution makes several things possible:
- Our Blender add-on can parse all properties of all Components without
having to create instances.
- Code readability: It is easy to see what the default value of a variable
is and whether or not it is required.
- Anything that can be defined in YAML files is now an AbstractProperty.
- Code completion in IDEs is still possible.
- EnumProperty makes validation easier if only a limited set of choices is
allowed.
"""

from typing import Any, Type, Callable, Union
import enum

import pogona as pg


[docs]class AbstractProperty: def __new__(cls, default: Any, required: bool, *args, **kwargs): """ :param default: :param required: If True, this property must be user-configured (i.e., the default value is irrelevant). :param args: :param kwargs: """ x = super().__new__(cls, default) x.required = required return x
[docs]class IntProperty(AbstractProperty, int): pass # intentional
[docs]class BoolProperty(AbstractProperty, int): # Python doesn't allow subclassing bool; using this workaround: # https://jfine-python-classes.readthedocs.io/en/latest/subclass-int.html def __new__(cls, default, required: bool, *args, **kwargs): return super().__new__( cls, default=bool(default), required=required, *args, **kwargs, ) def __repr__(self): return ['False', 'True'][self] # (self is an int)
[docs]class StrProperty(AbstractProperty, str): pass # intentional
[docs]class FloatProperty(AbstractProperty, float): pass # intentional
[docs]class FloatArrayProperty(AbstractProperty, list): """ Reserved for lists of floats of arbitrary lengths. For vectors of length 3 use VectorProperty (has additional checks in Component.initialize). """ pass # intentional
[docs]class VectorProperty(AbstractProperty, list): """ Reserved for lists of floats of length 3. Validated in Component.initialize. """ pass # intentional
[docs]class ListOfVectorsProperty(AbstractProperty, list): """ List of length-3 lists of floats. """ pass # intentional, for now
[docs]class EnumProperty(AbstractProperty, str): def __new__( cls, default: str, name: str, required: bool, enum_class: Type[enum.Enum], *args, **kwargs, ): """ :param default: :param name: Name of this property. Only used in an error message in case the default value is invalid. :param required: :param enum_class: :param args: :param kwargs: """ pg.util.check_enum_key( enum_class=enum_class, key=default, param_name=name, ) x = super().__new__(cls, default, required, *args, **kwargs) x.property_name = name x.property_enum_class = enum_class return x
# TODO: can we make this behave as Enum instead of as str without # breaking config files?
[docs]class ComponentReferenceProperty(AbstractProperty, str): def __new__( cls, default: str, required: bool, can_be_empty: 'Union[bool, Callable[[pg.Component], bool]]', *args, **kwargs, ): """ :param default: :param required: If True, this property must be user-configured (i.e., the default value is irrelevant). :param can_be_empty: If True, a user may override the default value with an empty string. :param args: :param kwargs: """ x = super().__new__(cls, default, required, *args, **kwargs) if callable(can_be_empty): x.property_can_be_empty = can_be_empty else: x.property_can_be_empty = lambda component: can_be_empty return x