summaryrefslogtreecommitdiff
path: root/factory
diff options
context:
space:
mode:
Diffstat (limited to 'factory')
-rw-r--r--factory/base.py57
-rw-r--r--factory/django.py2
2 files changed, 32 insertions, 27 deletions
diff --git a/factory/base.py b/factory/base.py
index 029185b..0db8249 100644
--- a/factory/base.py
+++ b/factory/base.py
@@ -40,6 +40,7 @@ FACTORY_CLASS_DECLARATION = 'FACTORY_FOR'
CLASS_ATTRIBUTE_DECLARATIONS = '_declarations'
CLASS_ATTRIBUTE_POSTGEN_DECLARATIONS = '_postgen_declarations'
CLASS_ATTRIBUTE_ASSOCIATED_CLASS = '_associated_class'
+CLASS_ATTRIBUTE_IS_ABSTRACT = '_abstract_factory'
class FactoryError(Exception):
@@ -101,10 +102,6 @@ class FactoryMetaClass(type):
Returns:
class: the class to associate with this factory
-
- Raises:
- AssociatedClassError: If we were unable to associate this factory
- to a class.
"""
if FACTORY_CLASS_DECLARATION in attrs:
return attrs[FACTORY_CLASS_DECLARATION]
@@ -114,10 +111,8 @@ class FactoryMetaClass(type):
if inherited is not None:
return inherited
- raise AssociatedClassError(
- "Could not determine the class associated with %s. "
- "Use the FACTORY_FOR attribute to specify an associated class." %
- class_name)
+ # Nothing found, return None.
+ return None
@classmethod
def _extract_declarations(mcs, bases, attributes):
@@ -155,7 +150,7 @@ class FactoryMetaClass(type):
return attributes
- def __new__(mcs, class_name, bases, attrs, extra_attrs=None):
+ def __new__(mcs, class_name, bases, attrs):
"""Record attributes as a pattern for later instance construction.
This is called when a new Factory subclass is defined; it will collect
@@ -166,9 +161,6 @@ class FactoryMetaClass(type):
bases (list of class): the parents of the class being created
attrs (str => obj dict): the attributes as defined in the class
definition
- extra_attrs (str => obj dict): extra attributes that should not be
- included in the factory defaults, even if public. This
- argument is only provided by extensions of this metaclass.
Returns:
A new class
@@ -178,18 +170,20 @@ class FactoryMetaClass(type):
return super(FactoryMetaClass, mcs).__new__(
mcs, class_name, bases, attrs)
- is_abstract = attrs.pop('ABSTRACT_FACTORY', False)
extra_attrs = {}
- if not is_abstract:
+ is_abstract = attrs.pop('ABSTRACT_FACTORY', False)
- base = parent_factories[0]
+ base = parent_factories[0]
+ inherited_associated_class = getattr(base,
+ CLASS_ATTRIBUTE_ASSOCIATED_CLASS, None)
+ associated_class = mcs._discover_associated_class(class_name, attrs,
+ inherited_associated_class)
- inherited_associated_class = getattr(base,
- CLASS_ATTRIBUTE_ASSOCIATED_CLASS, None)
- associated_class = mcs._discover_associated_class(class_name, attrs,
- inherited_associated_class)
+ if associated_class is None:
+ is_abstract = True
+ else:
# If inheriting the factory from a parent, keep a link to it.
# This allows to use the sequence counters from the parents.
if associated_class == inherited_associated_class:
@@ -197,21 +191,23 @@ class FactoryMetaClass(type):
# The CLASS_ATTRIBUTE_ASSOCIATED_CLASS must *not* be taken into
# account when parsing the declared attributes of the new class.
- extra_attrs = {CLASS_ATTRIBUTE_ASSOCIATED_CLASS: associated_class}
+ extra_attrs[CLASS_ATTRIBUTE_ASSOCIATED_CLASS] = associated_class
+
+ extra_attrs[CLASS_ATTRIBUTE_IS_ABSTRACT] = is_abstract
# Extract pre- and post-generation declarations
attributes = mcs._extract_declarations(parent_factories, attrs)
-
- # Add extra args if provided.
- if extra_attrs:
- attributes.update(extra_attrs)
+ attributes.update(extra_attrs)
return super(FactoryMetaClass, mcs).__new__(
mcs, class_name, bases, attributes)
def __str__(cls):
- return '<%s for %s>' % (cls.__name__,
- getattr(cls, CLASS_ATTRIBUTE_ASSOCIATED_CLASS).__name__)
+ if cls._abstract_factory:
+ return '<%s (abstract)>'
+ else:
+ return '<%s for %s>' % (cls.__name__,
+ getattr(cls, CLASS_ATTRIBUTE_ASSOCIATED_CLASS).__name__)
# Factory base classes
@@ -238,6 +234,9 @@ class BaseFactory(object):
# Holds the target class, once resolved.
_associated_class = None
+ # Whether this factory is considered "abstract", thus uncallable.
+ _abstract_factory = False
+
# List of arguments that should be passed as *args instead of **kwargs
FACTORY_ARG_PARAMETERS = ()
@@ -367,6 +366,12 @@ class BaseFactory(object):
create (bool): whether to 'build' or 'create' the object
attrs (dict): attributes to use for generating the object
"""
+ if cls._abstract_factory:
+ raise FactoryError(
+ "Cannot generate instances of abstract factory %(f)s; "
+ "Ensure %(f)s.FACTORY_FOR is set and %(f)s.ABSTRACT_FACTORY "
+ "is either not set or False." % dict(f=cls))
+
# Extract declarations used for post-generation
postgen_declarations = getattr(cls,
CLASS_ATTRIBUTE_POSTGEN_DECLARATIONS)
diff --git a/factory/django.py b/factory/django.py
index 1672fcc..e3e8829 100644
--- a/factory/django.py
+++ b/factory/django.py
@@ -49,7 +49,7 @@ class DjangoModelFactory(base.Factory):
handle those for non-numerical primary keys.
"""
- ABSTRACT_FACTORY = True
+ ABSTRACT_FACTORY = True # Optional, but explicit.
FACTORY_DJANGO_GET_OR_CREATE = ()
@classmethod