diff options
-rw-r--r-- | factory/containers.py | 110 |
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 |