Tutorial

Welcome to PyFactory!

This is a whirlwind tour of the features of PyFactory and a quick guide on how to use each of them.

What is PyFactory?

PyFactory is a library for writing and using model factories. Model factories allow you to replace test fixtures or manual model creation in tests with succint, easy to use factories.

PyFactory is also model-agnostic, meaning it can generate any sort of models for any ORM or backing store.

The specific feature list of PyFactory:

  • Model-agnostic. Generate models for any ORM or backing store.
  • Generate attributes. Instead of getting a full-blown model, you can request just the attributes of the model as a dictionary.
  • Build model objects (and optionally ask for them saved to the backing store as well).
  • Model complex associations in your schemas to generate all dependencies along with the model.

Your First Factory

Let’s create your first factory. Before doing that, however, we need to define what our models are. In your case, this may be a model for Django, SQLAlchemy, or something custom. For the purposes of our examples here, we’re going to simply set attributes on an object as our model.

The Model Builder

First, we need to create a model builder. This is the class which knows how to build our models given a dictionary of attributse. This is the core piece of PyFactory which enables model-agnosticism. Model builders are extremely easy to write, especially in our case:

class MyModelBuilder(object):
    @classmethod
    def build(cls, model_cls, attrs):
        result = model_cls()
        for key,val in attrs.iteritems():
            setattr(result, key, val)

        return result

    @classmethod
    def create(cls, model_cls, attrs):
        result = self.build(model_cls, attrs)
        result.saved = True
        return result

As you can see, given a model class and attributes, the model builder knows how to build them. In our case, this is simply using setattr on an object.

In-depth documentation is available on model builders.

The Factory

Now that we have a model builder, let’s go forward and build a simple factory. Let’s pretend we have a User model we want to create a factory for:

from pyfactory import Factory, schema

class UserFactory(Factory):
    _model = object
    _model_builder = MyModelBuilder

    @schema()
    def basic(self):
        return {
            "first_name": "John",
            "last_name": "Doe"
        }

Given the above factory, we can now generate users!

user = UserFactory().create("basic")
print user.first_name, user.last_name # "John Doe"

Using Your Factory

In the example above we used the create method to use our factory. There are other methods available as well:

  • attributes - This method will return the attributes of the model it would create as a dict.
  • build - This method will build the model but will not save it to the backing store. This is useful if you want a model to manipulate more before saving it.
  • create - This method will build the model and save it to the backing store.

Using these methods is straightforward:

factory = UserFactory()
factory.attributes("basic")
factory.build("basic")
factory.create("basic")

Associations

Most models, especially those in relational databases, have associations with other models. In the past, if you had a model which had many associations, you would have to manually create all these associations and maintain the brittle relationships. With PyFactory, these associations are managed for you.

Let’s create another factory, for a hypothetical Post which has an author, which is a User that we created a factory for moments before.

from pyfactory import Factory, association, schema

class PostFactory(Factory):
    _model = object
    _model_builder = MyModelBuilder

    @schema()
    def basic(self):
        return {
            "title": "My Post",
            "author": association(UserFactory(), "basic")
        }

The new piece of the above factory, of course, is the association function call. This means that the author field is an association to a UserFactory() factory and the basic schema of that factory, which is the schema we created earlier.

Given the above example, we can now create a Post which already has a User dependency created for us!

post = PostFactory().create("basic")
print post.title # => "My Post"
print post.author.first_name, post.author.last_name # => "John Doe"

Sequences

Sometimes, in order to help generate information that looks different from other information, it is useful to incorporate sequences of data. For example, instead of having ever user’s first name to “User” it would be nice if it could be “User #1,” “User #2,” etc. Sequences make this easy.

from pyfactory import Factory, schema, sequence

class UserFactory(Factory):
    _model = object
    _model_builder = MyModelBuilder

    @schema()
    def basic(self):
        return {
            "first_name": sequence("User %(n)d")
        }

Given the above example, we can now create User objects which have sequential first names:

user1 = UserFactory().create("basic")
user2 = UserFactory().create("basic")

print user1.first_name # => "User 1"
print user2.first_name # => "User 2"