Source code for configparserenhanced.TypedProperty

#!/usr/bin/env python3
# -*- mode: python; py-indent-offset: 4; py-continuation-offset: 4 -*-
#===============================================================================
# Copyright Notice
# ----------------
# Copyright 2021 National Technology & Engineering Solutions of Sandia,
# LLC (NTESS). Under the terms of Contract DE-NA0003525 with NTESS,
# the U.S. Government retains certain rights in this software.
#
# License (3-Clause BSD)
# ----------------------
# Copyright 2021 National Technology & Engineering Solutions of Sandia,
# LLC (NTESS). Under the terms of Contract DE-NA0003525 with NTESS,
# the U.S. Government retains certain rights in this software.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above
#    copyright notice, this list of conditions and the following
#    disclaimer in the documentation and/or other materials provided
#    with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its
#    contributors may be used to endorse or promote products derived
#    from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#===============================================================================
"""
"""
import copy
import typing



[docs]class SENTINEL: pass
[docs]def typed_property( name: str, expected_type=(int, str), default=SENTINEL, default_factory=lambda: None, req_assign_before_use=False, internal_type=None, validator=None, transform=None ): """ Implements a typed property in a class using the pattern `"9.21 Avoiding Repetitive Property Methods" from the O'Reilly Python Cookbook, 3rd Edition <https://learning.oreilly.com/library/view/python-cookbook-3rd/9781449357337/>`_. Args: name (str): The name of the property to create. expected_type (type,tuple): The *type* or a *tuple of types* enumerating allowable types to be assigned to the property. Default is ``(int,str)`` default: A default value to be assigned to the tuple. Default is ``None``. This will be assigned without type checking. default_factory: Default factory method. This must be callable but is used when we need a complex type that can't use ``deepcopy``. Default: ``lambda: None``. req_assign_before_use (bool): If ``True`` then raise an exception if the value is used before assigned. Otherwise, the *default* value is used. Default: ``False`` internal_type (<type>): Sets the ``<type>`` that the value is stored as (via typecast) internally. This is done during *assignment*. validator (func): A special validation function that can be called during assignment to provide additional checks such as list size, allowable values, etc. If the validator's return value is *truthy* the check suceeds, otherwise the check has failed and a ``ValueError`` will be raised. Default=None (i.e., no extra validation). transform (func): A function that can be used to transform the value before assignment. Raises: TypeError: if the assigned value is of the wrong type on assigmment. ValueError: if a *validator* is provided and the check fails (is Falsy). UnboundLocalError: If ``req_assign_before_use`` is True and an attempt to read the property is made before it's been assigned. """ varname = "_" + name varname_set = varname + "_is_set" expected_type = expected_type validator = validator @property def prop(self): if not hasattr(self, varname_set): setattr(self, varname_set, False) if req_assign_before_use is True and getattr(self, varname_set) is False: raise UnboundLocalError("Property {} referenced before assigned.".format(name)) if not hasattr(self, varname): if default is not SENTINEL: setattr(self, varname, copy.deepcopy(default)) else: if not callable(default_factory): raise TypeError( "default_factory `{}` in `{}` must be callable.".format(default_factory, name) ) setattr(self, varname, default_factory()) return getattr(self, varname) @prop.setter def prop(self, value): _expected_type = copy.deepcopy(expected_type) if not isinstance(_expected_type, typing.Iterable): _expected_type = (_expected_type, ) for expected_type_i in _expected_type: if isinstance(value, expected_type_i): break else: type_names = [i.__name__ for i in _expected_type] raise TypeError("'{}' must be in ({})".format(name, ",".join(type_names))) if internal_type is not None: value = internal_type(value) if transform is not None: if callable(transform): value = transform(value) if internal_type is not None: value = internal_type(value) else: raise TypeError(f"transform '{transform}' for property '{name}' is not callable.") if validator is not None: if callable(validator): if not validator(value): raise ValueError( f"Assignment of `{value}` to property `{name}` " + f"failed validation check in `{validator}`" ) else: raise TypeError(f"Validator '{validator}' for property '{name}' is not callable.") # Assign the value to the property setattr(self, varname, value) # Save that we've assigned the value to something setattr(self, varname_set, True) @prop.deleter def prop(self): if hasattr(self, varname): delattr(self, varname) if hasattr(self, varname_set): delattr(self, varname_set) return prop