summaryrefslogtreecommitdiff
path: root/factory/containers.py
diff options
context:
space:
mode:
Diffstat (limited to 'factory/containers.py')
-rw-r--r--factory/containers.py122
1 files changed, 105 insertions, 17 deletions
diff --git a/factory/containers.py b/factory/containers.py
index 0ae354b..4961115 100644
--- a/factory/containers.py
+++ b/factory/containers.py
@@ -25,13 +25,10 @@ import logging
logger = logging.getLogger(__name__)
from . import declarations
+from . import errors
from . import utils
-class CyclicDefinitionError(Exception):
- """Raised when cyclic definition were found."""
-
-
class LazyStub(object):
"""A generic container that only allows getting attributes.
@@ -93,7 +90,7 @@ class LazyStub(object):
attributes being computed.
"""
if name in self.__pending:
- raise CyclicDefinitionError(
+ raise errors.CyclicDefinitionError(
"Cyclic lazy attribute definition for %s; cycle found in %r." %
(name, self.__pending))
elif name in self.__values:
@@ -102,8 +99,10 @@ class LazyStub(object):
val = self.__attrs[name]
if isinstance(val, LazyValue):
self.__pending.append(name)
- val = val.evaluate(self, self.__containers)
- last = self.__pending.pop()
+ try:
+ val = val.evaluate(self, self.__containers)
+ finally:
+ last = self.__pending.pop()
assert name == last
self.__values[name] = val
return val
@@ -112,7 +111,6 @@ class LazyStub(object):
"The parameter %s is unknown. Evaluated attributes are %r, "
"definitions are %r." % (name, self.__values, self.__attrs))
-
def __setattr__(self, name, value):
"""Prevent setting attributes once __init__ is done."""
if not self.__initialized:
@@ -121,6 +119,69 @@ class LazyStub(object):
raise AttributeError('Setting of object attributes is not allowed')
+class DeclarationStack(object):
+ """An ordered stack of declarations.
+
+ This is intended to handle declaration precedence among different mutating layers.
+ """
+ def __init__(self, ordering):
+ self.ordering = ordering
+ self.layers = dict((name, {}) for name in self.ordering)
+
+ def __getitem__(self, key):
+ return self.layers[key]
+
+ def __setitem__(self, key, value):
+ assert key in self.ordering
+ self.layers[key] = value
+
+ def current(self):
+ """Retrieve the current, flattened declarations dict."""
+ result = {}
+ for layer in self.ordering:
+ result.update(self.layers[layer])
+ return result
+
+
+class ParameterResolver(object):
+ """Resolve a factory's parameter declarations."""
+ def __init__(self, parameters, deps):
+ self.parameters = parameters
+ self.deps = deps
+ self.declaration_stack = None
+
+ self.resolved = set()
+
+ def resolve_one(self, name):
+ """Compute one field is needed, taking dependencies into accounts."""
+ if name in self.resolved:
+ return
+
+ for dep in self.deps.get(name, ()):
+ self.resolve_one(dep)
+
+ self.compute(name)
+ self.resolved.add(name)
+
+ def compute(self, name):
+ """Actually compute the value for a given name."""
+ value = self.parameters[name]
+ if isinstance(value, declarations.ComplexParameter):
+ overrides = value.compute(name, self.declaration_stack.current())
+ else:
+ overrides = {name: value}
+ self.declaration_stack['overrides'].update(overrides)
+
+ def resolve(self, declaration_stack):
+ """Resolve parameters for a given declaration stack.
+
+ Modifies the stack in-place.
+ """
+ self.declaration_stack = declaration_stack
+ for name in self.parameters:
+ self.resolve_one(name)
+
+
class LazyValue(object):
"""Some kind of "lazy evaluating" object."""
@@ -129,7 +190,7 @@ class LazyValue(object):
raise NotImplementedError("This is an abstract method.")
-class OrderedDeclarationWrapper(LazyValue):
+class DeclarationWrapper(LazyValue):
"""Lazy wrapper around an OrderedDeclaration.
Attributes:
@@ -140,7 +201,7 @@ class OrderedDeclarationWrapper(LazyValue):
"""
def __init__(self, declaration, sequence, create, extra=None, **kwargs):
- super(OrderedDeclarationWrapper, self).__init__(**kwargs)
+ super(DeclarationWrapper, self).__init__(**kwargs)
self.declaration = declaration
self.sequence = sequence
self.create = create
@@ -170,7 +231,7 @@ class AttributeBuilder(object):
Attributes:
factory (base.Factory): the Factory for which attributes are being
built
- _attrs (DeclarationDict): the attribute declarations for the factory
+ _declarations (DeclarationDict): the attribute declarations for the factory
_subfields (dict): dict mapping an attribute name to a dict of
overridden default values for the related SubFactory.
"""
@@ -183,20 +244,47 @@ class AttributeBuilder(object):
self.factory = factory
self._containers = extra.pop('__containers', ())
- self._attrs = factory.declarations(extra)
+
+ initial_declarations = dict(factory._meta.declarations)
self._log_ctx = log_ctx
- initial_declarations = factory.declarations({})
+ # Parameters
+ # ----------
+ self._declarations = self.merge_declarations(initial_declarations, extra)
+
+ # Subfields
+ # ---------
+
attrs_with_subfields = [
k for k, v in initial_declarations.items()
- if self.has_subfields(v)]
+ if self.has_subfields(v)
+ ]
+ # Extract subfields; THIS MODIFIES self._declarations.
self._subfields = utils.multi_extract_dict(
- attrs_with_subfields, self._attrs)
+ attrs_with_subfields, self._declarations)
def has_subfields(self, value):
return isinstance(value, declarations.ParameteredAttribute)
+ def merge_declarations(self, initial, extra):
+ """Compute the final declarations, taking into account paramter-based overrides."""
+ # Precedence order:
+ # - Start with class-level declarations
+ # - Add overrides from parameters
+ # - Finally, use callsite-level declarations & values
+ declaration_stack = DeclarationStack(['initial', 'overrides', 'extra'])
+ declaration_stack['initial'] = initial.copy()
+ declaration_stack['extra'] = extra.copy()
+
+ # Actually compute the final stack
+ resolver = ParameterResolver(
+ parameters=self.factory._meta.parameters,
+ deps=self.factory._meta.parameters_dependencies,
+ )
+ resolver.resolve(declaration_stack)
+ return declaration_stack.current()
+
def build(self, create, force_sequence=None):
"""Build a dictionary of attributes.
@@ -214,9 +302,9 @@ class AttributeBuilder(object):
# Parse attribute declarations, wrapping SubFactory and
# OrderedDeclaration.
wrapped_attrs = {}
- for k, v in self._attrs.items():
+ for k, v in self._declarations.items():
if isinstance(v, declarations.OrderedDeclaration):
- v = OrderedDeclarationWrapper(v,
+ v = DeclarationWrapper(v,
sequence=sequence,
create=create,
extra=self._subfields.get(k, {}),