diff options
author | Thomas Goirand <thomas@goirand.fr> | 2013-11-20 19:47:48 +0800 |
---|---|---|
committer | Thomas Goirand <thomas@goirand.fr> | 2013-11-20 19:47:48 +0800 |
commit | 1935fece8dc7ad5e4b24198fd0bf9c93ec80daa2 (patch) | |
tree | 45abcb6788dd589c45d3cacb7a8c6347dd097761 /factory/base.py | |
parent | 47a9ddc907a828ad079e5cdee6ed8c942af96d5e (diff) | |
parent | 4523d576e9ad73f7ef14515bffc7c99c6184cdfd (diff) | |
download | factory-boy-1935fece8dc7ad5e4b24198fd0bf9c93ec80daa2.tar factory-boy-1935fece8dc7ad5e4b24198fd0bf9c93ec80daa2.tar.gz |
Merge branch 'debian/unstable' of ssh://git.gplhost.com/var/cache/git/openstack/factory-boy into debian/unstable
Conflicts:
debian/control
Diffstat (limited to 'factory/base.py')
-rw-r--r-- | factory/base.py | 636 |
1 files changed, 352 insertions, 284 deletions
diff --git a/factory/base.py b/factory/base.py index 62131fb..2ff2944 100644 --- a/factory/base.py +++ b/factory/base.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # Copyright (c) 2010 Mark Sandstrom -# Copyright (c) 2011 Raphaël Barrois +# Copyright (c) 2011-2013 Raphaël Barrois # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -20,121 +20,71 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -import re -import sys -import warnings - -from factory import containers +from . import containers # Strategies BUILD_STRATEGY = 'build' CREATE_STRATEGY = 'create' STUB_STRATEGY = 'stub' -# Creation functions. Use Factory.set_creation_function() to set a creation function appropriate for your ORM. -DJANGO_CREATION = lambda class_to_create, **kwargs: class_to_create.objects.create(**kwargs) - -# Building functions. Use Factory.set_building_function() to set a building functions appropriate for your ORM. -NAIVE_BUILD = lambda class_to_build, **kwargs: class_to_build(**kwargs) -MOGO_BUILD = lambda class_to_build, **kwargs: class_to_build.new(**kwargs) - # Special declarations FACTORY_CLASS_DECLARATION = 'FACTORY_FOR' # Factory class attributes CLASS_ATTRIBUTE_DECLARATIONS = '_declarations' +CLASS_ATTRIBUTE_POSTGEN_DECLARATIONS = '_postgen_declarations' CLASS_ATTRIBUTE_ASSOCIATED_CLASS = '_associated_class' +class FactoryError(Exception): + """Any exception raised by factory_boy.""" + + +class AssociatedClassError(FactoryError): + """Exception for Factory subclasses lacking FACTORY_FOR.""" + + +class UnknownStrategy(FactoryError): + """Raised when a factory uses an unknown strategy.""" + + +class UnsupportedStrategy(FactoryError): + """Raised when trying to use a strategy on an incompatible Factory.""" + + # Factory metaclasses def get_factory_bases(bases): - """Retrieve all BaseFactoryMetaClass-derived bases from a list.""" - return [b for b in bases if isinstance(b, BaseFactoryMetaClass)] + """Retrieve all FactoryMetaClass-derived bases from a list.""" + return [b for b in bases if issubclass(b, BaseFactory)] -class BaseFactoryMetaClass(type): +class FactoryMetaClass(type): """Factory metaclass for handling ordered declarations.""" def __call__(cls, **kwargs): - """Override the default Factory() syntax to call the default build strategy. + """Override the default Factory() syntax to call the default strategy. Returns an instance of the associated class. """ - if cls.default_strategy == BUILD_STRATEGY: + if cls.FACTORY_STRATEGY == BUILD_STRATEGY: return cls.build(**kwargs) - elif cls.default_strategy == CREATE_STRATEGY: + elif cls.FACTORY_STRATEGY == CREATE_STRATEGY: return cls.create(**kwargs) - elif cls.default_strategy == STUB_STRATEGY: + elif cls.FACTORY_STRATEGY == STUB_STRATEGY: return cls.stub(**kwargs) else: - raise BaseFactory.UnknownStrategy('Unknown default_strategy: {0}'.format(cls.default_strategy)) - - def __new__(cls, class_name, bases, attrs, extra_attrs=None): - """Record attributes as a pattern for later instance construction. - - This is called when a new Factory subclass is defined; it will collect - attribute declaration from the class definition. - - Args: - class_name (str): the name of the class being created - 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 - """ - - parent_factories = get_factory_bases(bases) - if not parent_factories or attrs.get('ABSTRACT_FACTORY', False): - # If this isn't a subclass of Factory, or specifically declared - # abstract, don't do anything special. - return super(BaseFactoryMetaClass, cls).__new__(cls, class_name, bases, attrs) - - declarations = containers.DeclarationDict() - - # Add parent declarations in reverse order. - for base in reversed(parent_factories): - # Import all 'public' attributes (avoid those starting with _) - declarations.update_with_public(getattr(base, CLASS_ATTRIBUTE_DECLARATIONS, {})) - - # Import attributes from the class definition, storing protected/private - # attributes in 'non_factory_attrs'. - non_factory_attrs = declarations.update_with_public(attrs) - - # Store the DeclarationDict in the attributes of the newly created class - non_factory_attrs[CLASS_ATTRIBUTE_DECLARATIONS] = declarations - - # Add extra args if provided. - if extra_attrs: - non_factory_attrs.update(extra_attrs) - - return super(BaseFactoryMetaClass, cls).__new__(cls, class_name, bases, non_factory_attrs) - - -class FactoryMetaClass(BaseFactoryMetaClass): - """Factory metaclass for handling class association and ordered declarations.""" - - ERROR_MESSAGE = """Could not determine what class this factory is for. - Use the {0} attribute to specify a class.""" - ERROR_MESSAGE_AUTODISCOVERY = ERROR_MESSAGE + """ - Also, autodiscovery failed using the name '{1}' - based on the Factory name '{2}' in {3}.""" + raise UnknownStrategy('Unknown FACTORY_STRATEGY: {0}'.format( + cls.FACTORY_STRATEGY)) @classmethod - def _discover_associated_class(cls, class_name, attrs, inherited=None): + def _discover_associated_class(mcs, class_name, attrs, inherited=None): """Try to find the class associated with this factory. In order, the following tests will be performed: - Lookup the FACTORY_CLASS_DECLARATION attribute - - If the newly created class is named 'FooBarFactory', look for a FooBar - class in its module - If an inherited associated class was provided, use it. Args: @@ -151,78 +101,112 @@ class FactoryMetaClass(BaseFactoryMetaClass): AssociatedClassError: If we were unable to associate this factory to a class. """ - own_associated_class = None - used_auto_discovery = False - if FACTORY_CLASS_DECLARATION in attrs: return attrs[FACTORY_CLASS_DECLARATION] - # No specific associated calss was given, and one was defined for our + # No specific associated class was given, and one was defined for our # parent, use it. if inherited is not None: return inherited - if '__module__' in attrs: - factory_module = sys.modules[attrs['__module__']] - if class_name.endswith('Factory'): - # Try a module lookup - used_auto_discovery = True - associated_name = class_name[:-len('Factory')] - if associated_name and hasattr(factory_module, associated_name): - warnings.warn( - "Auto-discovery of associated class is deprecated, and " - "will be removed in the future. Please set '%s = %s' " - "in the %s class definition." % ( - FACTORY_CLASS_DECLARATION, - associated_name, - class_name, - ), PendingDeprecationWarning) - - return getattr(factory_module, associated_name) - - # Unable to guess a good option; return the inherited class. - # Unable to find an associated class; fail. - if used_auto_discovery: - raise Factory.AssociatedClassError( - FactoryMetaClass.ERROR_MESSAGE_AUTODISCOVERY.format( - FACTORY_CLASS_DECLARATION, - associated_name, - class_name, - factory_module,)) - else: - raise Factory.AssociatedClassError( - FactoryMetaClass.ERROR_MESSAGE.format( - FACTORY_CLASS_DECLARATION)) + raise AssociatedClassError( + "Could not determine the class associated with %s. " + "Use the FACTORY_FOR attribute to specify an associated class." % + class_name) + + @classmethod + def _extract_declarations(mcs, bases, attributes): + """Extract declarations from a class definition. - def __new__(cls, class_name, bases, attrs): - """Determine the associated class based on the factory class name. Record the associated class - for construction of an associated class instance at a later time.""" + Args: + bases (class list): parent Factory subclasses + attributes (dict): attributes declared in the class definition + + Returns: + dict: the original attributes, where declarations have been moved to + _declarations and post-generation declarations to + _postgen_declarations. + """ + declarations = containers.DeclarationDict() + postgen_declarations = containers.PostGenerationDeclarationDict() + + # Add parent declarations in reverse order. + for base in reversed(bases): + # Import parent PostGenerationDeclaration + postgen_declarations.update_with_public( + getattr(base, CLASS_ATTRIBUTE_POSTGEN_DECLARATIONS, {})) + # Import all 'public' attributes (avoid those starting with _) + declarations.update_with_public( + getattr(base, CLASS_ATTRIBUTE_DECLARATIONS, {})) + + # Import attributes from the class definition + attributes = postgen_declarations.update_with_public(attributes) + # Store protected/private attributes in 'non_factory_attrs'. + attributes = declarations.update_with_public(attributes) + + # Store the DeclarationDict in the attributes of the newly created class + attributes[CLASS_ATTRIBUTE_DECLARATIONS] = declarations + attributes[CLASS_ATTRIBUTE_POSTGEN_DECLARATIONS] = postgen_declarations + + return attributes + + def __new__(mcs, class_name, bases, attrs, extra_attrs=None): + """Record attributes as a pattern for later instance construction. + + This is called when a new Factory subclass is defined; it will collect + attribute declaration from the class definition. + + Args: + class_name (str): the name of the class being created + 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 + """ parent_factories = get_factory_bases(bases) - if not parent_factories or attrs.get('ABSTRACT_FACTORY', False): - # If this isn't a subclass of Factory, don't do anything special. - return super(FactoryMetaClass, cls).__new__(cls, class_name, bases, attrs) + if not parent_factories: + return super(FactoryMetaClass, mcs).__new__( + mcs, class_name, bases, attrs) - base = parent_factories[0] + is_abstract = attrs.pop('ABSTRACT_FACTORY', False) + extra_attrs = {} - inherited_associated_class = getattr(base, - CLASS_ATTRIBUTE_ASSOCIATED_CLASS, None) - associated_class = cls._discover_associated_class(class_name, attrs, - inherited_associated_class) + if not is_abstract: - # Remove the FACTORY_CLASS_DECLARATION attribute from attrs, if present. - attrs.pop(FACTORY_CLASS_DECLARATION, None) + base = parent_factories[0] - # 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: - attrs['_base_factory'] = base + inherited_associated_class = getattr(base, + CLASS_ATTRIBUTE_ASSOCIATED_CLASS, None) + associated_class = mcs._discover_associated_class(class_name, attrs, + inherited_associated_class) - # 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} + # 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: + attrs['_base_factory'] = base - return super(FactoryMetaClass, cls).__new__(cls, class_name, bases, attrs, extra_attrs=extra_attrs) + # 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} + + # 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) + + 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__) # Factory base classes @@ -230,15 +214,13 @@ class FactoryMetaClass(BaseFactoryMetaClass): class BaseFactory(object): """Factory base support for sequences, attributes and stubs.""" - class UnknownStrategy(RuntimeError): - pass - - class UnsupportedStrategy(RuntimeError): - pass + # Backwards compatibility + UnknownStrategy = UnknownStrategy + UnsupportedStrategy = UnsupportedStrategy def __new__(cls, *args, **kwargs): """Would be called if trying to instantiate the class.""" - raise RuntimeError('You cannot instantiate BaseFactory') + raise FactoryError('You cannot instantiate BaseFactory') # ID to use for the next 'declarations.Sequence' attribute. _next_sequence = None @@ -248,6 +230,15 @@ class BaseFactory(object): # class. _base_factory = None + # Holds the target class, once resolved. + _associated_class = None + + # List of arguments that should be passed as *args instead of **kwargs + FACTORY_ARG_PARAMETERS = () + + # List of attributes that should not be passed to the underlying class + FACTORY_HIDDEN_ARGS = () + @classmethod def _setup_next_sequence(cls): """Set up an initial sequence value for Sequence attributes. @@ -291,7 +282,13 @@ class BaseFactory(object): applicable; the current list of computed attributes is available to the currently processed object. """ - return containers.AttributeBuilder(cls, extra).build(create) + force_sequence = None + if extra: + force_sequence = extra.pop('__sequence', None) + return containers.AttributeBuilder(cls, extra).build( + create=create, + force_sequence=force_sequence, + ) @classmethod def declarations(cls, extra_defs=None): @@ -304,9 +301,108 @@ class BaseFactory(object): return getattr(cls, CLASS_ATTRIBUTE_DECLARATIONS).copy(extra_defs) @classmethod + def _adjust_kwargs(cls, **kwargs): + """Extension point for custom kwargs adjustment.""" + return kwargs + + @classmethod + def _prepare(cls, create, **kwargs): + """Prepare an object for this factory. + + Args: + create: bool, whether to create or to build the object + **kwargs: arguments to pass to the creation function + """ + target_class = getattr(cls, CLASS_ATTRIBUTE_ASSOCIATED_CLASS) + kwargs = cls._adjust_kwargs(**kwargs) + + # Remove 'hidden' arguments. + for arg in cls.FACTORY_HIDDEN_ARGS: + del kwargs[arg] + + # Extract *args from **kwargs + args = tuple(kwargs.pop(key) for key in cls.FACTORY_ARG_PARAMETERS) + + if create: + return cls._create(target_class, *args, **kwargs) + else: + return cls._build(target_class, *args, **kwargs) + + @classmethod + def _generate(cls, create, attrs): + """generate the object. + + Args: + create (bool): whether to 'build' or 'create' the object + attrs (dict): attributes to use for generating the object + """ + # Extract declarations used for post-generation + postgen_declarations = getattr(cls, + CLASS_ATTRIBUTE_POSTGEN_DECLARATIONS) + postgen_attributes = {} + for name, decl in sorted(postgen_declarations.items()): + postgen_attributes[name] = decl.extract(name, attrs) + + # Generate the object + obj = cls._prepare(create, **attrs) + + # Handle post-generation attributes + results = {} + for name, decl in sorted(postgen_declarations.items()): + extracted, extracted_kwargs = postgen_attributes[name] + results[name] = decl.call(obj, create, extracted, + **extracted_kwargs) + + cls._after_postgeneration(obj, create, results) + + return obj + + @classmethod + def _after_postgeneration(cls, obj, create, results=None): + """Hook called after post-generation declarations have been handled. + + Args: + obj (object): the generated object + create (bool): whether the strategy was 'build' or 'create' + results (dict or None): result of post-generation declarations + """ + pass + + @classmethod + def _build(cls, target_class, *args, **kwargs): + """Actually build an instance of the target_class. + + Customization point, will be called once the full set of args and kwargs + has been computed. + + Args: + target_class (type): the class for which an instance should be + built + args (tuple): arguments to use when building the class + kwargs (dict): keyword arguments to use when building the class + """ + return target_class(*args, **kwargs) + + @classmethod + def _create(cls, target_class, *args, **kwargs): + """Actually create an instance of the target_class. + + Customization point, will be called once the full set of args and kwargs + has been computed. + + Args: + target_class (type): the class for which an instance should be + created + args (tuple): arguments to use when creating the class + kwargs (dict): keyword arguments to use when creating the class + """ + return target_class(*args, **kwargs) + + @classmethod def build(cls, **kwargs): """Build an instance of the associated class, with overriden attrs.""" - raise cls.UnsupportedStrategy() + attrs = cls.attributes(create=False, extra=kwargs) + return cls._generate(False, attrs) @classmethod def build_batch(cls, size, **kwargs): @@ -314,16 +410,17 @@ class BaseFactory(object): Args: size (int): the number of instances to build - + Returns: object list: the built instances """ - return [cls.build(**kwargs) for _ in xrange(size)] + return [cls.build(**kwargs) for _ in range(size)] @classmethod def create(cls, **kwargs): """Create an instance of the associated class, with overriden attrs.""" - raise cls.UnsupportedStrategy() + attrs = cls.attributes(create=True, extra=kwargs) + return cls._generate(True, attrs) @classmethod def create_batch(cls, size, **kwargs): @@ -331,11 +428,11 @@ class BaseFactory(object): Args: size (int): the number of instances to create - + Returns: object list: the created instances """ - return [cls.create(**kwargs) for _ in xrange(size)] + return [cls.create(**kwargs) for _ in range(size)] @classmethod def stub(cls, **kwargs): @@ -345,7 +442,7 @@ class BaseFactory(object): factory's declarations or in the extra kwargs. """ stub_object = containers.StubObject() - for name, value in cls.attributes(create=False, extra=kwargs).iteritems(): + for name, value in cls.attributes(create=False, extra=kwargs).items(): setattr(stub_object, name, value) return stub_object @@ -355,11 +452,11 @@ class BaseFactory(object): Args: size (int): the number of instances to stub - + Returns: object list: the stubbed instances """ - return [cls.stub(**kwargs) for _ in xrange(size)] + return [cls.stub(**kwargs) for _ in range(size)] @classmethod def generate(cls, strategy, **kwargs): @@ -428,189 +525,160 @@ class BaseFactory(object): return cls.generate_batch(strategy, size, **kwargs) -class StubFactory(BaseFactory): - __metaclass__ = BaseFactoryMetaClass - - default_strategy = STUB_STRATEGY - - -class Factory(BaseFactory): - """Factory base with build and create support. +Factory = FactoryMetaClass('Factory', (BaseFactory,), { + 'ABSTRACT_FACTORY': True, + 'FACTORY_STRATEGY': CREATE_STRATEGY, + '__doc__': """Factory base with build and create support. This class has the ability to support multiple ORMs by using custom creation functions. - """ - __metaclass__ = FactoryMetaClass + """, + }) - default_strategy = CREATE_STRATEGY - class AssociatedClassError(RuntimeError): - pass - - # Customizing 'create' strategy, using a tuple to keep the creation function - # from turning it into an instance method. - _creation_function = (DJANGO_CREATION,) +# Backwards compatibility +Factory.AssociatedClassError = AssociatedClassError # pylint: disable=W0201 - def __str__(self): - return '<%s for %s>' % (self.__class__.__name__, - getattr(self, CLASS_ATTRIBUTE_ASSOCIATED_CLASS).__name__) - @classmethod - def set_creation_function(cls, creation_function): - """Set the creation function for this class. +class StubFactory(Factory): - Args: - creation_function (function): the new creation function. That - function should take one non-keyword argument, the 'class' for - which an instance will be created. The value of the various - fields are passed as keyword arguments. - """ - cls._creation_function = (creation_function,) - - @classmethod - def get_creation_function(cls): - """Retrieve the creation function for this class. - - Returns: - function: A function that takes one parameter, the class for which - an instance will be created, and keyword arguments for the value - of the fields of the instance. - """ - return cls._creation_function[0] - - # Customizing 'build' strategy, using a tuple to keep the creation function - # from turning it into an instance method. - _building_function = (NAIVE_BUILD,) - - @classmethod - def set_building_function(cls, building_function): - """Set the building function for this class. - - Args: - building_function (function): the new building function. That - function should take one non-keyword argument, the 'class' for - which an instance will be built. The value of the various - fields are passed as keyword arguments. - """ - cls._building_function = (building_function,) - - @classmethod - def get_building_function(cls): - """Retrieve the building function for this class. - - Returns: - function: A function that takes one parameter, the class for which - an instance will be created, and keyword arguments for the value - of the fields of the instance. - """ - return cls._building_function[0] - - @classmethod - def _prepare(cls, create, **kwargs): - """Prepare an object for this factory. - - Args: - create: bool, whether to create or to build the object - **kwargs: arguments to pass to the creation function - """ - if create: - return cls.get_creation_function()(getattr(cls, CLASS_ATTRIBUTE_ASSOCIATED_CLASS), **kwargs) - else: - return cls.get_building_function()(getattr(cls, CLASS_ATTRIBUTE_ASSOCIATED_CLASS), **kwargs) - - @classmethod - def _build(cls, **kwargs): - return cls._prepare(create=False, **kwargs) - - @classmethod - def _create(cls, **kwargs): - return cls._prepare(create=True, **kwargs) + FACTORY_STRATEGY = STUB_STRATEGY + FACTORY_FOR = containers.StubObject @classmethod def build(cls, **kwargs): - return cls._build(**cls.attributes(create=False, extra=kwargs)) + raise UnsupportedStrategy() @classmethod def create(cls, **kwargs): - return cls._create(**cls.attributes(create=True, extra=kwargs)) + raise UnsupportedStrategy() class DjangoModelFactory(Factory): """Factory for Django models. - This makes sure that the 'sequence' field of created objects is an unused id. + This makes sure that the 'sequence' field of created objects is a new id. Possible improvement: define a new 'attribute' type, AutoField, which would handle those for non-numerical primary keys. """ ABSTRACT_FACTORY = True + FACTORY_DJANGO_GET_OR_CREATE = () + + @classmethod + def _get_manager(cls, target_class): + try: + return target_class._default_manager # pylint: disable=W0212 + except AttributeError: + return target_class.objects @classmethod def _setup_next_sequence(cls): - """Compute the next available ID, based on the 'id' database field.""" + """Compute the next available PK, based on the 'pk' database field.""" + + model = cls._associated_class # pylint: disable=E1101 + manager = cls._get_manager(model) + try: - return 1 + cls._associated_class._default_manager.values_list('id', flat=True - ).order_by('-id')[0] + return 1 + manager.values_list('pk', flat=True + ).order_by('-pk')[0] except IndexError: return 1 + @classmethod + def _get_or_create(cls, target_class, *args, **kwargs): + """Create an instance of the model through objects.get_or_create.""" + manager = cls._get_manager(target_class) + + assert 'defaults' not in cls.FACTORY_DJANGO_GET_OR_CREATE, ( + "'defaults' is a reserved keyword for get_or_create " + "(in %s.FACTORY_DJANGO_GET_OR_CREATE=%r)" + % (cls, cls.FACTORY_DJANGO_GET_OR_CREATE)) + + key_fields = {} + for field in cls.FACTORY_DJANGO_GET_OR_CREATE: + key_fields[field] = kwargs.pop(field) + key_fields['defaults'] = kwargs + + obj, _created = manager.get_or_create(*args, **key_fields) + return obj + + @classmethod + def _create(cls, target_class, *args, **kwargs): + """Create an instance of the model, and save it to the database.""" + manager = cls._get_manager(target_class) + + if cls.FACTORY_DJANGO_GET_OR_CREATE: + return cls._get_or_create(target_class, *args, **kwargs) -def make_factory(klass, **kwargs): - """Create a new, simple factory for the given class.""" - factory_name = '%sFactory' % klass.__name__ - kwargs[FACTORY_CLASS_DECLARATION] = klass - factory_class = type(Factory).__new__(type(Factory), factory_name, (Factory,), kwargs) - factory_class.__name__ = '%sFactory' % klass.__name__ - factory_class.__doc__ = 'Auto-generated factory for class %s' % klass - return factory_class + return manager.create(*args, **kwargs) + @classmethod + def _after_postgeneration(cls, obj, create, results=None): + """Save again the instance if creating and at least one hook ran.""" + if create and results: + # Some post-generation hooks ran, and may have modified us. + obj.save() -def build(klass, **kwargs): - """Create a factory for the given class, and build an instance.""" - return make_factory(klass, **kwargs).build() +class MogoFactory(Factory): + """Factory for mogo objects.""" + ABSTRACT_FACTORY = True -def build_batch(klass, size, **kwargs): - """Create a factory for the given class, and build a batch of instances.""" - return make_factory(klass, **kwargs).build_batch(size) + @classmethod + def _build(cls, target_class, *args, **kwargs): + return target_class.new(*args, **kwargs) -def create(klass, **kwargs): - """Create a factory for the given class, and create an instance.""" - return make_factory(klass, **kwargs).create() +class BaseDictFactory(Factory): + """Factory for dictionary-like classes.""" + ABSTRACT_FACTORY = True + @classmethod + def _build(cls, target_class, *args, **kwargs): + if args: + raise ValueError( + "DictFactory %r does not support FACTORY_ARG_PARAMETERS.", cls) + return target_class(**kwargs) -def create_batch(klass, size, **kwargs): - """Create a factory for the given class, and create a batch of instances.""" - return make_factory(klass, **kwargs).create_batch(size) + @classmethod + def _create(cls, target_class, *args, **kwargs): + return cls._build(target_class, *args, **kwargs) -def stub(klass, **kwargs): - """Create a factory for the given class, and stub an instance.""" - return make_factory(klass, **kwargs).stub() +class DictFactory(BaseDictFactory): + FACTORY_FOR = dict -def stub_batch(klass, size, **kwargs): - """Create a factory for the given class, and stub a batch of instances.""" - return make_factory(klass, **kwargs).stub_batch(size) +class BaseListFactory(Factory): + """Factory for list-like classes.""" + ABSTRACT_FACTORY = True + @classmethod + def _build(cls, target_class, *args, **kwargs): + if args: + raise ValueError( + "ListFactory %r does not support FACTORY_ARG_PARAMETERS.", cls) -def generate(klass, strategy, **kwargs): - """Create a factory for the given class, and generate an instance.""" - return make_factory(klass, **kwargs).generate(strategy) + values = [v for k, v in sorted(kwargs.items())] + return target_class(values) + @classmethod + def _create(cls, target_class, *args, **kwargs): + return cls._build(target_class, *args, **kwargs) -def generate_batch(klass, strategy, size, **kwargs): - """Create a factory for the given class, and generate instances.""" - return make_factory(klass, **kwargs).generate_batch(strategy, size) +class ListFactory(BaseListFactory): + FACTORY_FOR = list -def simple_generate(klass, create, **kwargs): - """Create a factory for the given class, and simple_generate an instance.""" - return make_factory(klass, **kwargs).simple_generate(create) +def use_strategy(new_strategy): + """Force the use of a different strategy. -def simple_generate_batch(klass, create, size, **kwargs): - """Create a factory for the given class, and simple_generate instances.""" - return make_factory(klass, **kwargs).simple_generate_batch(create, size) + This is an alternative to setting default_strategy in the class definition. + """ + def wrapped_class(klass): + klass.FACTORY_STRATEGY = new_strategy + return klass + return wrapped_class |