Giter Club home page Giter Club logo

flask-fixtures's Introduction

Flask-Fixtures

A simple library that allows you to add database fixtures for your unit tests using nothing but JSON or YAML.

Installation

Installing Flask-Fixtures is simple, just do a typical pip install like so:

pip install flask-fixtures

If you are going to use JSON as your data serialization format, you
should also consider installing the dateutil package since it will
add much more powerful and flexible parsing of dates and times.

To install the library from source simply download the source code, or check it out if you have git installed on your system, then just run the install command.

git clone https://github.com/croach/Flask-Fixtures.git
cd /path/to/flask-fixtures
python setup.py install

Setup

To setup the library, you simply need to tell Flask-Fixtures where it can find the fixtures files for your tests. Fixtures can reside anywhere on the file system, but by default, Flask-Fixtures looks for these files in a directory called fixtures in your app's root directory. To add more directories to the list to be searched, just add an attribute called FIXTURES_DIRS to your app's config object. This attribute should be a list of strings, where each string is a path to a fixtures directory. Absolute paths are added as is, but reltative paths will be relative to your app's root directory.

Once you have configured the extension, you can begin adding fixtures for your tests.

Adding Fixtures

To add a set of fixtures, you simply add any number of JSON or YAML files describing the individual fixtures to be added to your test database into one of the directories you specified in the FIXTURES_DIRS attribute, or into the default fixtures directory. As an example, I'm going to assume we have a Flask application with the following directory structure.

/myapp
    __init__.py
    config.py
    models.py
    /fixtures
        authors.json

The __init__.py file will be responsible for creating our Flask application object.

# myapp/__init__.py

from flask import Flask

app = Flask(__name__)

The config.py object holds our test configuration file.

# myapp/config.py

class TestConfig(object):
    SQLALCHEMY_DATABASE_URI = 'sqlite://'
    testing = True
    debug = True

And, finally, inside of the models.py files we have the following database models.

# myapp/models.py

from flask_sqlalchemy import SQLAlchemy

from myapp import app

db = SQLAlchemy(app)

class Author(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    first_name = db.Column(db.String(30))
    last_name = db.Column(db.String(30))

class Book(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(200))
    author_id = db.Column(db.Integer, db.ForeignKey('author.id'))
    author = db.relationship('Author', backref='books')

Given the model classes above, if we wanted to mock up some data for our database, we could do so in single file, or we could even split our fixtures into multiple files each corresponding to a single model class. For this simple example, we'll go with one file that we'll call authors.json.

A fixtures file contains a list of objects. Each object contains a key called records that holds another list of objects each representing either a row in a table, or an instance of a model. If you wish to work with tables, you'll need to specify the name of the table with the table key. If you'd prefer to work with models, specify the fully-qualified class name of the model using the model key. Once you've specified the table or model you want to work with, you'll need to specify the data associated with each table row, or model instance. Each object in the records list will hold the data for a single row or model. The example below is the JSON for a single author record and a few books associated with that author. Create a file called myapp/fixtures/authors.json and copy and paste the fixtures JSON below into that file.

[
    {
        "table": "author",
        "records": [{
            "id": 1,
            "first_name": "William",
            "last_name": "Gibson",
        }]
    },
    {
        "model": "myapp.models.Book",
        "records": [{
            "title": "Neuromancer",
            "author_id": 1
        },
        {
            "title": "Count Zero",
            "author_id": 1
        },
        {
            "title": "Mona Lisa Overdrive",
            "author_id": 1
        }]
    }
]

Another option, if you have PyYAML installed, is to write your fixtures using the YAML syntax instead of JSON. Personally, I prefer to use YAML; I find its syntax is easier to read, and I find the ability to add comments to my fixtures to be invaluable.

If you'd prefer to use YAML, I've added a version of the authors.json file written in YAML below. Just copy and paste it into a file called myapp/fixtures/authors.yaml in place of creating the JSON file above.

- table: author
  records:
    - id: 1
      first_name: William
      last_name: Gibson

- model: myapp.models.Book
  records:
    - title: Neuromancer
      author_id: 1
      published_date: 1984-07-01
    - title: Count Zero
      author_id: 1
      published_date: 1986-03-01
    - title: Neuromancer
      author_id: 1
      published_date: 1988-10-01

After reading over the previous section, you might be asking yourself why the library supports two methods for adding records to the database. There are a few good reasons for supporting both tables and models when creating fixtures. Using tables is faster, since we can take advantage of SQLAlchemy's bulk insert to add several records at once. However, to do so, you must first make sure that the records list is homegenous. In other words, every object in the ``records`` list must have the same set of key/value pairs, otherwise the bulk insert will not work. Using models, however, allows you to have a heterogenous list of record objects.

The other reason you may want to use models instead of tables is that you'll be able to take advantage of any python-level defaults, checks, etc. that you have setup on the model. Using a table, bypasses the model completely and inserts the data directly into the database, which means you'll need to think on a lower level when creating table-based fixtures.

Usage

To use Flask-Fixtures in your unit tests, you'll need to make sure your test class inherits from FixturesMixin and that you've specified a list of fixtures files to load. The sample code below shows how to do each these steps.

First, make sure the app that you're testing is initialized with the proper configuration. Then import and initialize the FixturesMixin class, create a new test class, and inherit from FixturesMixin. Now you just need to tell Flask-Fixtures which fixtures files to use for your tests. You can do so by setting the fixtures class variable. Doing so will setup and tear down fixtures between each test. To persist fixtures across tests, i.e., to setup fixtures only when the class is first created and tear them down after all tests have finished executing, you'll need to set the persist_fixtures variable to True. The fixtures variable should be set to a list of strings, each of which is the name of a fixtures file to load. Flask-Fixtures will then search the default fixtures directory followed by each directory in the FIXTURES_DIRS config variable, in order, for a file matching each name in the list and load each into the test database.

# myapp/fixtures/test_fixtures.py

import unittest

from myapp import app
from myapp.models import db, Book, Author

from flask_fixtures import FixturesMixin

# Configure the app with the testing configuration
app.config.from_object('myapp.config.TestConfig')


# Make sure to inherit from the FixturesMixin class
class TestFoo(unittest.TestCase, FixturesMixin):

    # Specify the fixtures file(s) you want to load.
    # Change the list below to ['authors.yaml'] if you created your fixtures
    # file using YAML instead of JSON.
    fixtures = ['authors.json']

    # Specify the Flask app and db we want to use for this set of tests
    app = app
    db = db

    # Your tests go here

    def test_authors(self):
        authors = Author.query.all()
        assert len(authors) == Author.query.count() == 1
        assert len(authors[0].books) == 3

    def test_books(self):
        books = Book.query.all()
        assert len(books) == Book.query.count() == 3
        gibson = Author.query.filter(Author.last_name=='Gibson').one()
        for book in books:
            assert book.author == gibson

Examples

To see the library in action, you can find a simple Flask application and set of unit tests matching the ones in the example above in the tests/myapp directory. To run these examples yourself, just follow the directions below for "Contributing to Flask-Fixtures".

Contributing to Flask-Fixtures

Currently, Flask-Fixtures supports python versions 2.6 and 2.7 and the py.test, nose, and unittest (included in the python standard library) libraries. To contribute bug fixes and features to Flask-Fixtures, you'll need to make sure that any code you contribute does not break any of the existing unit tests in any of these environments.

To run unit tests in all six of the supported environments, I suggest you install tox and simply run the tox command. If, however, you insist on running things by hand, you'll need to create a virtualenv for both python 2.6 and python 2.7. Then, install nose and py.test in each virtualenv. Finally, you can run the tests with the commands in the table below.

Library Command
py.test py.test
nose nosetests
unittest python -m unittest discover --start-directory tests

flask-fixtures's People

Contributors

croach avatar ropp avatar sbusch-mobivention avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

flask-fixtures's Issues

Add the ability to nest fixtures

The idea here is that, instead of creating two separate fixture objects, when one is a child of the other, it would be nice if we could nest them. For example, assume we have the following classes:

class Author(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    first_name = db.Column(db.String(30))
    last_name = db.Column(db.String(30))

class Book(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(200))
    author = db.relationship('Author', backref='books')

It would be nice to be able to do something like this in the fixtures data:

[
    {
        "table": "authors",
        "fields": {
            "first_name": "Christopher",
                "last_name": "Roach",
                "books": [{
                    "title": "Flask in Action"
                }]
        }
    }
]

Flask-Fixtures should be able to match up attribute names against relationships and add them accordingly.

Add default current directory for fixtures

Right now we check the current directory for fixtures only if none are passed into the decorator, but there's no reason we shouldn't check the current directory at all times.

update PyPi docs

PyPi docs example show use of the FixturesMixin module which is does not exist in the 0.3.1 release.

example

Should be an easy fix. I use this package everyday. Thanks for your work on it!

Possible issue with bit fields

This may be an issue with me not understanding fixtures and/or SQLAlchemy, but any assistance would be great!

I have some data dumped from an MS SQL DB, which is using BIT fields for true/false. I'm trying to load it into a PostgreSQL DB for testing. The data is exported by getting the records (via SQLAlchemy), and then looping over them and generating each dict thus:

def as_dict(r):
    d = {}

    for c in r.__table__.columns:
        o = getattr(r, c.name)
        d[c.name]= o

    return d

That works great, properly formats UUIDs and date/times. However, the bit fields come out as 'true' or 'false.' While that is what they are being used as, this then creates issues later. When I try to load them into the PG DB (again, via Flask-Fixtures), I get an exception of:

sqlalchemy.exc.ProgrammingError: (psycopg2.ProgrammingError) column "isActive" is of type bit but expression is of type boolean

If I change the true/false entries to 1/0 in the Yaml file, I then get:

sqlalchemy.exc.ProgrammingError: (psycopg2.ProgrammingError) column "isActive" is of type bit but expression is of type integer

Is there a way to mark them such that Flask-Fixtures will turn them back into Bit objects before it tries to insert them? Does Flask-Fixtures introspect on the column

I realize the proper answer here is "convert them to boolean fields," but this is a legacy database, and quick-and-dirty changes to the schema would not happen soon. :)

This may well be an issue with SQLAlchemy, as it seems it converts from the bit field to a true/false value when selecting, but then does not convert back when inserting the value.

How DO you pass a bit object to SQLAlchemy?

Run fixtures in every test

I would like to be able to run the fixtures every test, for starting each one in a clean and controlled way.

Right now, it seems that only sets the database and the fixtures at the beginning, so I'm having conflicts in two different tests, each one tests user creation, but the second throws an error cos the email (unique index in Database) was created in the previous test.

So, my question is how I can run the fixtures every test, despite of slowing down the suite.

Application not registered on db instance and no application bound to current context

setup fails when SQLAlchemy.init_app() is used to initialize the application for the database.

Example:

Works

app = Flask(__name__)
db = SQLAlchemy(app)
FixturesMixin.init_app(app, db)

Doesn't work

app = Flask(__name__)
db = SQLAlchemy()
db.init_app(app)
FixturesMixin.init_app(app, db)

This creates problems when creating the application through a factory function.

Allow multiple fixture sets within a single file

The idea here is to allow a developer to add multiple fixture sets within a single file and have only a specific set loaded for a test. You could do this by accepting a file name and then a list of fixture sets. This way you could have a default set of fixtures for every test and separate sets that are specific to each test.

SQLite gotcha

If you try to set a SQLAlchemy DateTime value using SQLite as the backing database, you will get

sqlalchemy.exc.StatementError
StatementError: SQLite Date type only accepts Python date objects as input.

Since you cannot really import datetime and set this in the json directly it be handled as the object is created

import datetime
created = datetime.utcnow()
cod = Code(created=created)
db.session.add(cod)
db.session.commit()

Or using a separate setter function.

Again this is a gotcha with SQLite that is up to you to either warn people about or provide a way for them to be set.

class level model didn't work

app.py

class model(object):

    class Book(db.Model):
        bookid = db.Column(db.Integer, primary_key=True)
        name = db.Column(db.Unicode, nullable=False)

bookdata.json

{
    "model": "app.model.Book",
    "records": []
}

the error is module app.model not exists.

perhaps use new sytanx app:model.Book can solve this problem.

Flask-Fixtures will parse string containing numbers as datetime

I have a simple database, and tried to load a fixture:
[ { "model": "app.models.Devices",
"records": [ { "uuid": "1", "name": "Agent" } ]
}]

it tries to parse the uuid as a datetime object. It should not. If I change the value of uuid to "a1" then it is properly a string.

The model is:
class Devices(db.Model):
tablename = 'devices'
name = Column(String, nullable=False)
uuid = Column(String, primary_key=True, nullable=False)

Keeping fixtures after unit tests

Hello,

Is there a way to keep the fixtures in the database after they are loaded? Like so I would be able to see them in my database and do a bit of manual testing for development.

The fixtures always get torn down no matter what I try!

Thank you

py.test seems not to be really supported

I tryed to use flask-fixtures for testing my project with py.test. It turned out that the only way to make fixtures load is to make the test class inherite from unittest.TestCase. Doing so make py.test run tests as unittest tests and act as a test container rather than the actual underlying test lib (it's at least how I understand what's going on here), and thus all the py.test fixture feature is deactivated. It's a huge issue, since I obviously need several fixture from pytest and pytest-flask to test my app (namely the client fixture). The claim that flask-fixtures support py.test seems to rely only on the fact that py.test can run many test lib like unittest. I think the doc should at least be clearer and warn about that, and maybe it would be even better not to claim to support py.test but just indicate that flask-fixtures tests can be run through py.test.

Anyway, I started to look into a way to make flask-fixtures fully support py.test, and if the project maintainer thinks it can be a good addition to the lib, I will go forward on this feature.

Database initialization

I am reading your README and I see an example about testing.

But is it possible to run your script for database data initialization ?

Many-to-many issue

I have faced an issue with Flask's db.Table. Here's my setup:

# models.py:
class Project(db.Model):
        id = db.Column(db.Integer, primary_key=True)
    interviewers = db.relationship('User', secondary='project_interviewer')

project_interviewer = db.Table(
    'project_interviewer',
    db.Column('user_id', db.Integer, db.ForeignKey('user.id')),
    db.Column('project_id', db.Integer, db.ForeignKey('project.id')),
)

Now I am trying to load project from the fixtures:

[{
  "model": "merku.models.Project",
  "records": [
    { "id": 1 }
  ]
}, {
  "table": "project_interviewer",
  "records": [
    {
      "id": 1,
      "project_id": 1,
      "user_id": 2
    }
  ]
}]

It was not loading. After some digging I have found out that the issue was here:

# flask_fixtures/__init__.py line: 82
table = Table(fixture['table'], metadata)

After looking inside this object, it became clear, that it was not creating any columns. So, the generated query was: INSERT INTO project_interviewer () VALUES ().

I have made this to work with:

table = Table(fixture['table'], metadata,
                      autoload=True, autoload_with=db.engine)
for record in fixture['records']:  
    # there was an issue 'default engine does not support multi-line inserts':
    conn.execute(table.insert(), record)

Maybe I have missed something?

PyYAML deprecation warning

The YAMLLoader's load(filename) method calls PyYAML's corresponding load() function without an explicit Loader class. This has been deprecated since PyYAML version 5.1 because of potential security issues and produces a warning (see also https://github.com/yaml/pyyaml/wiki/PyYAML-yaml.load(input)-Deprecation).

As PyYAML implicitely uses the safe FullLoader implementation if the parameter is omitted, this can be passed to its load() function without changing the behaviour of flask fixtures while still getting rid of the warning.

Add support for standalone test functions

The py.test and nose libraries support standalone test functions (the unittest standard library does not). I'd like to add support for these standalone functions to the Flask-Fixtures library.

If we wrap each function with a class that inherits from the FixturesMixin (a simplified example listed below) we'll be able to use standalone functions even when using the unittest library. This may be a good or bad thing since it means that decorated functions will work with unittest, but regular functions without the decorator will not, which may be a bit confusing to anyone writing tests. The alternative is just to wrap the test function in another function and the py.test and nose libraries will work properly, and unittest will continue to not work with any standalone functions.

import unittest
from functools import wraps

def bar(fn):
    @wraps(fn)
    def _test_fn(self):
        print
        print "do something before..."
        fn()
        print "do something after..."

    return type('Test_' + fn.__name__, (unittest.TestCase,), {
        fn.__name__: _test_fn
    })

@bar
def test():
    assert True

@bar
def test2():
    assert True

Doing per-test fixtures?

I came across this project today, and it appears to be exactly what I want. There is one feature that would make life a little easier for me. If this feature is there, please forgive intrusion (I haven't looked at the code).

Is there a way to specify the yaml/json file(s) per test? I have several tests involving the User class, for example, but don't want the same set of records for each test. I'd rather not have a proliferation of TestClasses just because I need different fixtures.

Thanks!

And if I missed anything about a mailing list, I apologize.

Add a simple variable replacement templating syntax for fixtures

In several situations, the developer has added constants to their code that the test test writer would like to reference in the tests. Doing so allows tests to remain unchanged when changes to the value of said constants happen in the codebase. This feature would add a pre-processing step to the ingestion of fixture files that would allow test writers to reference variables within python modules.

For example,

- table: employees
  records:
    - id: 1
      name: Winston Smith
      email: #{ oceania.models.WINSTON_SMITH_EMAIL }
{
    "table": "employees",
    "records": [{
        "id": 1,
        "name": "Winston Smith",
        "email": "#{ oceania.models.WINSTON_SMITH_EMAIL }"
    }]
}

Missing FixturesMixin

Hi,

I cannot seem to find the FixturesMixin mentioned in the doc. Maybe it's something evident and I'm just missing it ...

Can you please let me know how is that supposed to work?

Bests,

Yoanis.

Fix issue with existing fixtures sticking around between tests

If you use fixtures with a class that does it's own setup and teardown any tables that were already created stick around and that means that any data within them stick around as well. We should use the 'checkfirst' parameter on db.create_all and set it to False to make sure it creates all tables from scratch each time. Though, we might want to consider not using it on class level fixtures (need more research here).

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.