summaryrefslogtreecommitdiff
path: root/factory/containers.py
diff options
context:
space:
mode:
Diffstat (limited to 'factory/containers.py')
-rw-r--r--factory/containers.py110
1 files changed, 91 insertions, 19 deletions
diff --git a/factory/containers.py b/factory/containers.py
index d52ca05..e280162 100644
--- a/factory/containers.py
+++ b/factory/containers.py
@@ -20,9 +20,12 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
-from declarations import OrderedDeclaration, SubFactory
+from factory import declarations
+
+#: String for splitting an attribute name into a
+#: (subfactory_name, subfactory_field) tuple.
ATTR_SPLITTER = '__'
@@ -31,29 +34,48 @@ class CyclicDefinitionError(Exception):
class LazyStub(object):
- """A generic container that allows for getting but not setting of attributes.
+ """A generic container that only allows getting attributes.
+
+ Attributes are set at instantiation time, values are computed lazily.
- Attributes are set at initialization time."""
+ Attributes:
+ __initialized (bool): whether this object's __init__ as run. If set,
+ setting any attribute will be prevented.
+ __attrs (dict): maps attribute name to their declaration
+ __values (dict): maps attribute name to computed value
+ __pending (str list): names of the attributes whose value is being
+ computed. This allows to detect cyclic lazy attribute definition.
+ """
- initialized = False
+ __initialized = False
def __init__(self, attrs):
self.__attrs = attrs
self.__values = {}
self.__pending = []
- self.initialized = True
+ self.__initialized = True
def __fill__(self):
+ """Fill this LazyStub, computing values of all defined attributes.
+
+ Retunrs:
+ dict: map of attribute name => computed value
+ """
res = {}
for attr in self.__attrs:
res[attr] = getattr(self, attr)
return res
def __getattr__(self, name):
+ """Retrieve an attribute's value.
+
+ This will compute it if needed, unless it is already on the list of
+ attributes being computed.
+ """
if name in self.__pending:
raise CyclicDefinitionError(
- "Cyclic lazy attribute definition for %s. Current cycle is %r." %
- (name, self.__pending))
+ "Cyclic lazy attribute definition for %s; cycle found in %r." %
+ (name, self.__pending))
elif name in self.__values:
return self.__values[name]
elif name in self.__attrs:
@@ -66,17 +88,21 @@ class LazyStub(object):
return val
else:
raise AttributeError(
- "The parameter %s is unknown. Evaluated attributes are %r, definitions are %r." % (name, self.__values, self.__attrs))
+ "The parameter %s is unknown. Evaluated attributes are %r, "
+ "definitions are %r." % (name, self.__values, self.__attrs))
def __setattr__(self, name, value):
- if not self.initialized:
+ """Prevent setting attributes once __init__ is done."""
+ if not self.__initialized:
return super(LazyStub, self).__setattr__(name, value)
else:
raise AttributeError('Setting of object attributes is not allowed')
class DeclarationDict(dict):
+ """Slightly extended dict to work with OrderedDeclaration."""
+
def update_with_public(self, d):
"""Updates the DeclarationDict from a class definition dict.
@@ -87,13 +113,18 @@ class DeclarationDict(dict):
"""
remaining = {}
for k, v in d.iteritems():
- if k.startswith('_') and not isinstance(v, OrderedDeclaration):
+ if k.startswith('_') and not isinstance(v, declarations.OrderedDeclaration):
remaining[k] = v
else:
self[k] = v
return remaining
def copy(self, extra=None):
+ """Copy this DeclarationDict into another one, including extra values.
+
+ Args:
+ extra (dict): additional attributes to include in the copy.
+ """
new = DeclarationDict()
new.update(self)
if extra:
@@ -102,12 +133,25 @@ class DeclarationDict(dict):
class LazyValue(object):
+ """Some kind of "lazy evaluating" object."""
+
def evaluate(self, obj):
+ """Compute the value, using the given object."""
raise NotImplementedError("This is an abstract method.")
class SubFactoryWrapper(LazyValue):
- def __init__(self, subfactory, subfields, create):
+ """Lazy wrapper around a SubFactory.
+
+ Attributes:
+ subfactory (declarations.SubFactory): the SubFactory being wrapped
+ subfields (DeclarationDict): Default values to override when evaluating
+ the SubFactory
+ create (bool): whether to 'create' or 'build' the SubFactory.
+ """
+
+ def __init__(self, subfactory, subfields, create, *args, **kwargs):
+ super(SubFactoryWrapper, self).__init__(*args, **kwargs)
self.subfactory = subfactory
self.subfields = subfields
self.create = create
@@ -117,7 +161,17 @@ class SubFactoryWrapper(LazyValue):
class OrderedDeclarationWrapper(LazyValue):
- def __init__(self, declaration, sequence):
+ """Lazy wrapper around an OrderedDeclaration.
+
+ Attributes:
+ declaration (declarations.OrderedDeclaration): the OrderedDeclaration
+ being wrapped
+ sequence (int): the sequence counter to use when evaluatin the
+ declaration
+ """
+
+ def __init__(self, declaration, sequence, *args, **kwargs):
+ super(OrderedDeclarationWrapper, self).__init__(*args, **kwargs)
self.declaration = declaration
self.sequence = sequence
@@ -126,9 +180,19 @@ class OrderedDeclarationWrapper(LazyValue):
class AttributeBuilder(object):
- """Builds attributes from a factory and extra data."""
+ """Builds attributes from a factory and extra data.
+
+ Attributes:
+ factory (base.Factory): the Factory for which attributes are being
+ built
+ _attrs (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.
+ """
+
+ def __init__(self, factory, extra=None, *args, **kwargs):
+ super(AttributeBuilder, self).__init__(*args, **kwargs)
- def __init__(self, factory, extra=None):
if not extra:
extra = {}
self.factory = factory
@@ -136,30 +200,38 @@ class AttributeBuilder(object):
self._subfields = self._extract_subfields()
def _extract_subfields(self):
+ """Extract the subfields from the declarations list."""
sub_fields = {}
for key in list(self._attrs):
if ATTR_SPLITTER in key:
+ # Trying to define a default of a subfactory
cls_name, attr_name = key.split(ATTR_SPLITTER, 1)
if cls_name in self._attrs:
sub_fields.setdefault(cls_name, {})[attr_name] = self._attrs.pop(key)
return sub_fields
def build(self, create):
+ """Build a dictionary of attributes.
+
+ Args:
+ create (bool): whether to 'build' or 'create' the subfactories.
+ """
+ # Setup factory sequence.
self.factory.sequence = self.factory._generate_next_sequence()
+ # Parse attribute declarations, wrapping SubFactory and
+ # OrderedDeclaration.
wrapped_attrs = {}
for k, v in self._attrs.iteritems():
- if isinstance(v, SubFactory):
+ if isinstance(v, declarations.SubFactory):
v = SubFactoryWrapper(v, self._subfields.get(k, {}), create)
- elif isinstance(v, OrderedDeclaration):
+ elif isinstance(v, declarations.OrderedDeclaration):
v = OrderedDeclarationWrapper(v, self.factory.sequence)
wrapped_attrs[k] = v
- stub = LazyStub(wrapped_attrs)
- return stub.__fill__()
+ return LazyStub(wrapped_attrs).__fill__()
class StubObject(object):
"""A generic container."""
-
pass