# -*- coding: utf-8 -*-
import abc
import logging
from contextlib import contextmanager
from threading import RLock
import six
from sceptre.helpers import _call_func_on_values
[docs]class RecursiveGet(Exception):
pass
[docs]@six.add_metaclass(abc.ABCMeta)
class Resolver:
"""
Resolver is an abstract base class that should be inherited by all
Resolvers.
:param argument: The argument of the resolver.
:type argument: str
:param stack: The associated stack of the resolver.
:type stack: sceptre.stack.Stack
"""
__metaclass__ = abc.ABCMeta
def __init__(self, argument=None, stack=None):
self.logger = logging.getLogger(__name__)
self.argument = argument
self.stack = stack
[docs] def setup(self):
"""
This method is called at during stack initialisation.
Implementation of this method in subclasses can be used to do any
initial setup of the object.
"""
pass # pragma: no cover
[docs] @abc.abstractmethod
def resolve(self):
"""
An abstract method which must be overwritten by all inheriting classes.
This method is called to retrieve the final desired value.
Implementation of this method in subclasses must return a suitable
object or primitive type.
"""
pass # pragma: no cover
[docs]class ResolvableProperty(object):
"""
This is a descriptor class used to store an attribute that may contain
Resolver objects. When retrieving the dictionary or list, any Resolver
objects contains are a value or within a list are resolved to a primitive
type. Supports nested dictionary and lists.
:param name: Attribute suffix used to store the property in the instance.
:type name: str
"""
def __init__(self, name):
self.name = "_" + name
self.logger = logging.getLogger(__name__)
self._get_in_progress = False
self._lock = RLock()
def __get__(self, instance, type):
"""
Attribute getter which resolves any Resolver object contained in the
complex data structure.
:return: The attribute stored with the suffix ``name`` in the instance.
:rtype: dict or list
"""
with self._lock, self._no_recursive_get():
def resolve(attr, key, value):
try:
attr[key] = value.resolve()
except RecursiveGet:
attr[key] = self.ResolveLater(instance, self.name, key,
lambda: value.resolve())
if hasattr(instance, self.name):
retval = _call_func_on_values(
resolve, getattr(instance, self.name), Resolver
)
return retval
def __set__(self, instance, value):
"""
Attribute setter which adds a stack reference to any resolvers in the
data structure `value` and calls the setup method.
"""
def setup(attr, key, value):
value.stack = instance
value.setup()
with self._lock:
_call_func_on_values(setup, value, Resolver)
setattr(instance, self.name, value)
[docs] class ResolveLater(object):
"""Represents a value that could not yet be resolved but can be resolved in the future."""
def __init__(self, instance, name, key, resolution_function):
self._instance = instance
self._name = name
self._key = key
self._resolution_function = resolution_function
def __call__(self):
"""Resolve the value."""
attr = getattr(self._instance, self._name)
attr[self._key] = self._resolution_function()
@contextmanager
def _no_recursive_get(self):
if self._get_in_progress:
raise RecursiveGet()
self._get_in_progress = True
try:
yield
finally:
self._get_in_progress = False