Test django schema and data migrations, including migrations' order and best practices.

Overview

django-test-migrations

wemake.services Build status codecov Python Version PyPI - Django Version wemake-python-styleguide

Features

  • Allows to test django schema and data migrations
  • Allows to test both forward and rollback migrations
  • Allows to test the migrations order
  • Allows to test migration names
  • Allows to test database configuration
  • Fully typed with annotations and checked with mypy, PEP561 compatible
  • Easy to start: has lots of docs, tests, and tutorials

Read the announcing post. See real-world usage example.

Installation

pip install django-test-migrations

We support several django versions:

  • 1.11
  • 2.2
  • 3.0
  • 3.1

Other versions might work too, but they are not officially supported.

Testing django migrations

Testing migrations is not a frequent thing in django land. But, sometimes it is totally required. When?

When we do complex schema or data changes and what to be sure that existing data won't be corrupted. We might also want to be sure that all migrations can be safely rolled back. And as a final touch we want to be sure that migrations are in the correct order and have correct dependencies.

Testing forward migrations

To test all migrations we have a Migrator class.

It has three methods to work with:

  • .apply_initial_migration() which takes app and migration names to generate a state before the actual migration happens. It creates the before state by applying all migrations up to and including the ones passed as an argument.

  • .apply_tested_migration() which takes app and migration names to perform the actual migration

  • .reset() to clean everything up after we are done with testing

So, here's an example:

from django_test_migrations.migrator import Migrator

migrator = Migrator(database='default')

# Initial migration, currently our model has only a single string field:
# Note:
# We are testing migration `0002_someitem_is_clean`, so we are specifying
# the name of the previous migration (`0001_initial`) in the
# .apply_initial_migration() method in order to prepare a state of the database
# before applying the migration we are going to test.
#
old_state = migrator.apply_initial_migration(('main_app', '0001_initial'))
SomeItem = old_state.apps.get_model('main_app', 'SomeItem')

# Let's create a model with just a single field specified:
SomeItem.objects.create(string_field='a')
assert len(SomeItem._meta.get_fields()) == 2  # id + string_field

# Now this migration will add `is_clean` field to the model:
new_state = migrator.apply_tested_migration(
    ('main_app', '0002_someitem_is_clean'),
)
SomeItem = new_state.apps.get_model('main_app', 'SomeItem')

# We can now test how our migration worked, new field is there:
assert SomeItem.objects.filter(is_clean=True).count() == 0
assert len(SomeItem._meta.get_fields()) == 3  # id + string_field + is_clean

# Cleanup:
migrator.reset()

That was an example of a forward migration.

Backward migration

The thing is that you can also test backward migrations. Nothing really changes except migration names that you pass and your logic:

migrator = Migrator()

# Currently our model has two field, but we need a rollback:
old_state = migrator.apply_initial_migration(
    ('main_app', '0002_someitem_is_clean'),
)
SomeItem = old_state.apps.get_model('main_app', 'SomeItem')

# Create some data to illustrate your cases:
# ...

# Now this migration will drop `is_clean` field:
new_state = migrator.apply_tested_migration(('main_app', '0001_initial'))

# Assert the results:
# ...

# Cleanup:
migrator.reset()

Testing migrations ordering

Sometimes we also want to be sure that our migrations are in the correct order. And all our dependecies = [...] are correct.

To achieve that we have plan.py module.

That's how it can be used:

from django_test_migrations.plan import all_migrations, nodes_to_tuples

main_migrations = all_migrations('default', ['main_app', 'other_app'])
assert nodes_to_tuples(main_migrations) == [
    ('main_app', '0001_initial'),
    ('main_app', '0002_someitem_is_clean'),
    ('other_app', '0001_initial'),
    ('main_app', '0003_update_is_clean'),
    ('main_app', '0004_auto_20191119_2125'),
    ('other_app', '0002_auto_20191120_2230'),
]

This way you can be sure that migrations and apps that depend on each other will be executed in the correct order.

Test framework integrations 🐍

We support several test frameworks as first-class citizens. That's a testing tool after all!

Note that the Django post_migrate signal's receiver list is cleared at the start of tests and restored afterwards. If you need to test your own post_migrate signals then attach/remove them during a test.

pytest

We ship django-test-migrations with a pytest plugin that provides two convinient fixtures:

  • migrator_factory that gives you an opportunity to create Migrator classes for any database
  • migrator instance for the 'default' database

That's how it can be used:

import pytest

@pytest.mark.django_db()
def test_pytest_plugin_initial(migrator):
    """Ensures that the initial migration works."""
    old_state = migrator.apply_initial_migration(('main_app', None))

    with pytest.raises(LookupError):
        # Models does not yet exist:
        old_state.apps.get_model('main_app', 'SomeItem')

    new_state = migrator.apply_tested_migration(('main_app', '0001_initial'))
    # After the initial migration is done, we can use the model state:
    SomeItem = new_state.apps.get_model('main_app', 'SomeItem')
    assert SomeItem.objects.filter(string_field='').count() == 0

unittest

We also ship an integration with the built-in unittest framework.

Here's how it can be used:

from django_test_migrations.contrib.unittest_case import MigratorTestCase

class TestDirectMigration(MigratorTestCase):
    """This class is used to test direct migrations."""

    migrate_from = ('main_app', '0002_someitem_is_clean')
    migrate_to = ('main_app', '0003_update_is_clean')

    def prepare(self):
        """Prepare some data before the migration."""
        SomeItem = self.old_state.apps.get_model('main_app', 'SomeItem')
        SomeItem.objects.create(string_field='a')
        SomeItem.objects.create(string_field='a b')

    def test_migration_main0003(self):
        """Run the test itself."""
        SomeItem = self.new_state.apps.get_model('main_app', 'SomeItem')

        assert SomeItem.objects.count() == 2
        assert SomeItem.objects.filter(is_clean=True).count() == 1

Choosing only migrations tests

In CI systems it is important to get instant feedback. Running tests that apply database migration can slow down tests execution, so it is often a good idea to run standard, fast, regular unit tests without migrations in parallel with slower migrations tests.

pytest

django_test_migrations adds migration_test marker to each test using migrator_factory or migrator fixture. To run only migrations test, use -m option:

pytest -m migration_test  # runs only migraion tests
pytest -m "not migration_test"  # runs all except migraion tests

unittest

django_test_migrations adds migration_test tag to every MigratorTestCase subclass. To run only migrations tests, use --tag option:

python mange.py test --tag=migration_test  # runs only migraion tests
python mange.py test --exclude-tag=migration_test  # runs all except migraion tests

Django Checks

django_test_migrations comes with 2 groups of Django's checks for:

  • detecting migrations scripts automatically generated names
  • validating some subset of database settings

Testing migration names

django generates migration names for you when you run makemigrations. And these names are bad (read more about why it is bad)! Just look at this: 0004_auto_20191119_2125.py

What does this migration do? What changes does it have?

One can also pass --name attribute when creating migrations, but it is easy to forget.

We offer an automated solution: django check that produces an error for each badly named migration.

Add our check into your INSTALLED_APPS:

INSTALLED_APPS = [
    # ...

    # Our custom check:
    'django_test_migrations.contrib.django_checks.AutoNames',
]

And then in your CI run:

python manage.py check --deploy

This way you will be safe from wrong names in your migrations.

Do you have a migrations that cannot be renamed? Add them to the ignore list:

# settings.py

DTM_IGNORED_MIGRATIONS = {
    ('main_app', '0004_auto_20191119_2125'),
    ('dependency_app', '0001_auto_20201110_2100'),
}

And we won't complain about them.

Or you can completely ignore entire app:

# settings.py

DTM_IGNORED_MIGRATIONS = {
    ('dependency_app', '*'),
    ('another_dependency_app', '*'),
}

Database configuration

Add our check to INSTALLED_APPS:

INSTALLED_APPS = [
    # ...

    # Our custom check:
    'django_test_migrations.contrib.django_checks.DatabaseConfiguration',
]

Then just run check management command in your CI like listed in section above.

Related projects

You might also like:

  • django-migration-linter - Detect backward incompatible migrations for your django project.
  • wemake-django-template - Bleeding edge django template focused on code quality and security with both django-test-migrations and django-migration-linter on board.

Credits

This project is based on work of other awesome people:

License

MIT.

Comments
  • properly reset databases on tests teardown

    properly reset databases on tests teardown

    Currently multiple databases are supported by creating instance of Migrator for each database, but only default database is cleaned at the end of test - https://github.com/wemake-services/django-test-migrations/blob/master/django_test_migrations/migrator.py#L77

    Also using migrate management command is not the best idea when it comes to performance, because there is no need to apply all unapplied migrations just before the next test, which should start with clean state. Django's TestCase uses flush management command to clean databases after test.

    We need to reuse Django's code responsible for cleaning database on test teardown (or establish our own way to do that) and fix Migrator.reset() method to clean Migrator.database instead of default one.

    enhancement help wanted 
    opened by skarzi 22
  • apply_initial_migration fails

    apply_initial_migration fails

    Hi,

    I've just discovered this package and wanted to test a new migration that I am writing. So far, the project consists of a few apps, and the app I want to write migration tests for has currently > 100 applied migrations. For the sake of simplicity, I set migrate_from and migrate_to to an already applied migration (~101 -> 102, only a change in a field type) just to fiddle arround. Curiously, it failed.

    Traceback:

    `MySQLdb._exceptions.OperationalError: (3730, "Cannot drop table 'wagtailcore_collectionviewrestriction' referenced by a foreign key constraint 'wagtailcore_collecti_collectionviewrestri_47320efd_fk_wagtailco' on table 'wagtailcore_collectionviewrestriction_groups'.")
    
    env/lib/python3.6/site-packages/MySQLdb/connections.py:239: OperationalError
    
    The above exception was the direct cause of the following exception:
    env/lib/python3.6/site-packages/django_test_migrations/contrib/unittest_case.py:36: in setUp
        self.migrate_from,
    env/lib/python3.6/site-packages/django_test_migrations/migrator.py:46: in apply_initial_migration
        sql.drop_models_tables(self._database, style)
    env/lib/python3.6/site-packages/django_test_migrations/sql.py:32: in drop_models_tables
        get_execute_sql_flush_for(connection)(database_name, sql_drop_tables)
    env/lib/python3.6/site-packages/django/db/backends/base/operations.py:405: in execute_sql_flush
        cursor.execute(sql)
    env/lib/python3.6/site-packages/django/db/backends/utils.py:68: in execute
        return self._execute_with_wrappers(sql, params, many=False, executor=self._execute)
    env/lib/python3.6/site-packages/django/db/backends/utils.py:77: in _execute_with_wrappers
        return executor(sql, params, many, context)
    env/lib/python3.6/site-packages/django/db/backends/utils.py:86: in _execute
        return self.cursor.execute(sql, params)
    env/lib/python3.6/site-packages/django/db/utils.py:90: in __exit__
        raise dj_exc_value.with_traceback(traceback) from exc_value
    env/lib/python3.6/site-packages/django/db/backends/utils.py:84: in _execute
        return self.cursor.execute(sql)
    env/lib/python3.6/site-packages/django/db/backends/mysql/base.py:74: in execute
        return self.cursor.execute(query, args)
    env/lib/python3.6/site-packages/MySQLdb/cursors.py:209: in execute
        res = self._query(query)
    env/lib/python3.6/site-packages/MySQLdb/cursors.py:315: in _query
        db.query(q)
    

    and a lot more other errors related to drop queries. As I understand it, during setup, django-test-migrations takes the current test_db and deletes all models in the database, and then reapplies all migrations up and including to migrate_from. In my case, this seems to fail. I cannot imagine what I could have done wrong in my testcase, as the error occurs during the setUp function of the MigratorTestCase.

    I don't think that it has anything to do with wagtail, as the testcase seems to fail with different errors on each run:

    self = <_mysql.connection closed at 0x35419e8>
    query = b'DROP TABLE `auth_group` CASCADE'
    
        def query(self, query):
            # Since _mysql releases GIL while querying, we need immutable buffer.
            if isinstance(query, bytearray):
                query = bytes(query)
    >       _mysql.connection.query(self, query)
    E       django.db.utils.OperationalError: (3730, "Cannot drop table 'auth_group' referenced by a foreign key constraint 'auth_group_permissions_group_id_b120cbf9_fk_auth_group_id' on table 'auth_group_permissions'.")
    

    Testcase:

    class TestMigrations(MigratorTestCase):
        migrate_from = ('myapp', None)
        migrate_to = ('myapp', '001_initial')
    
        def prepare(self):
            pass
    
        def test_migration001(self):
            self.assertTrue(True)
    

    Maybe I am missing something crucial and obvious, so here is what I did:

    install django-test-migrations with pip wrote a unittest testcase, as provided in the example (the actual tc is just an assertTrue(True) ran manage.py test I did not add anything to INSTALLED_APPS whatsoever.

    Any ideas?

    DB: MYSQL 8.0.19 mysqlclient (1.4.6) Django: 3.0.2 django-test-migrations: 1.0.0

    opened by Hafnernuss 12
  • Tests with MigratorTestCase cause failures in other tests

    Tests with MigratorTestCase cause failures in other tests

    Hi, I'm working on a medium-sized Django codebase and recently introduced django-test-migrations to test our migrations. It's a great project and solves a need that many serious Django apps have. We'd like to see it improved by fixing a blocking issue we're seeing.

    Unfortunately, when our test suite isn't ordered in the usual fashion, I'm seeing failures. This is true for random ordering, and predictable but non-standard ordering (see below).

    I'm careful to draw conclusions, but to me it seems likely that the issue is with our use of django-test-migrations. It doesn't always appear, but it only appears when those tests are run.

    I'm a bit at loss about what might be causing the issue, and don't know how to debug further. I do have some work-time available to help debug this issue. I'd be interested to know if you've heard of this issue before. And please let me know how I can assist.

    Things I did

    • Ran test suite without new tests in random order multiple times: no failures
    • Ran test suite with new tests in random order: failures almost every time so far
    • Ran test suite with new tests in standard order: no failures
    • Ran test suite with new tests in parallel on CircleCI: failures
    • Ran test suite with new tests and clear ContentType cache before and after each test: failures. I did this because it was documented as possible solution here. Also see #10827.

    Offending tests

    The two tests that seem to be causing the unrelated failures are here: https://gist.github.com/maikhoepfel/0c4c52c86f047c79b85d8b4e98af194f I don't think they do anything special.

    The tests I added aren't the first tests for migrations. We've been employing something very similar to the blog post where it all started. Only when we added django-test-migrations did we see the issue. This is surprising to me, because the actual code executed is similar.

    The failures

    django.db.utils.IntegrityError: insert or update on table "core_stock" violates foreign key constraint "core_stock_content_type_id_d8f48c0c_fk_django_content_type_id" DETAIL:  Key (content_type_id)=(1) is not present in table "django_content_type".
    self = <django.db.backends.utils.CursorWrapper object at 0x7f4b5f881dd8>
    sql = 'SET CONSTRAINTS ALL IMMEDIATE', params = None
    ignored_wrapper_args = (False, {'connection': <django.db.backends.postgresql.base.DatabaseWrapper object at 0x7f4b675bd4a8>, 'cursor': <django.db.backends.utils.CursorWrapper object at 0x7f4b5f881dd8>})
    
        def _execute(self, sql, params, *ignored_wrapper_args):
            self.db.validate_no_broken_transaction()
            with self.db.wrap_database_errors:
                if params is None:
    >               return self.cursor.execute(sql)
    E               psycopg2.errors.ForeignKeyViolation: insert or update on table "core_stock" violates foreign key constraint "core_stock_content_type_id_d8f48c0c_fk_django_content_type_id"
    E               DETAIL:  Key (content_type_id)=(1) is not present in table "django_content_type".
    
    venv/lib/python3.6/site-packages/django/db/backends/utils.py:82: ForeignKeyViolation
    
    The above exception was the direct cause of the following exception:
    
    self = <tests.ondemand.views.test_views.StoreCartTestCase testMethod=test_checkout_cart_pricing_rule_limit_add_occurences>
    result = <TestCaseFunction test_checkout_cart_pricing_rule_limit_add_occurences>
    
        def __call__(self, result=None):
            """
            Wrapper around default __call__ method to perform common Django test
            set up. This means that user-defined Test Cases aren't required to
            include a call to super().setUp().
            """
            testMethod = getattr(self, self._testMethodName)
            skipped = (
                getattr(self.__class__, "__unittest_skip__", False) or
                getattr(testMethod, "__unittest_skip__", False)
            )
        
            if not skipped:
                try:
                    self._pre_setup()
                except Exception:
                    result.addError(self, sys.exc_info())
                    return
            super().__call__(result)
            if not skipped:
                try:
    >               self._post_teardown()
    
    venv/lib/python3.6/site-packages/django/test/testcases.py:274: 
    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
    venv/lib/python3.6/site-packages/django/test/testcases.py:1009: in _post_teardown
        self._fixture_teardown()
    venv/lib/python3.6/site-packages/django/test/testcases.py:1177: in _fixture_teardown
        connections[db_name].check_constraints()
    venv/lib/python3.6/site-packages/django/db/backends/postgresql/base.py:246: in check_constraints
        self.cursor().execute('SET CONSTRAINTS ALL IMMEDIATE')
    venv/lib/python3.6/site-packages/sentry_sdk/integrations/django/__init__.py:487: in execute
        return real_execute(self, sql, params)
    venv/lib/python3.6/site-packages/django/db/backends/utils.py:67: in execute
        return self._execute_with_wrappers(sql, params, many=False, executor=self._execute)
    venv/lib/python3.6/site-packages/django/db/backends/utils.py:76: in _execute_with_wrappers
        return executor(sql, params, many, context)
    venv/lib/python3.6/site-packages/django/db/backends/utils.py:82: in _execute
        return self.cursor.execute(sql)
    venv/lib/python3.6/site-packages/django/db/utils.py:89: in __exit__
        raise dj_exc_value.with_traceback(traceback) from exc_value
    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
    
    self = <django.db.backends.utils.CursorWrapper object at 0x7f4b5f881dd8>
    sql = 'SET CONSTRAINTS ALL IMMEDIATE', params = None
    ignored_wrapper_args = (False, {'connection': <django.db.backends.postgresql.base.DatabaseWrapper object at 0x7f4b675bd4a8>, 'cursor': <django.db.backends.utils.CursorWrapper object at 0x7f4b5f881dd8>})
    
        def _execute(self, sql, params, *ignored_wrapper_args):
            self.db.validate_no_broken_transaction()
            with self.db.wrap_database_errors:
                if params is None:
    >               return self.cursor.execute(sql)
    E               django.db.utils.IntegrityError: insert or update on table "core_stock" violates foreign key constraint "core_stock_content_type_id_d8f48c0c_fk_django_content_type_id"
    E               DETAIL:  Key (content_type_id)=(1) is not present in table "django_content_type".
    
    venv/lib/python3.6/site-packages/django/db/backends/utils.py:82: IntegrityError
    
    opened by maiksprenger 9
  • Confusing before/after methods.

    Confusing before/after methods.

    These two methods do the same thing.

    Why not call it migrate and reload the graph each time?

    From my understanding before(('app', '0003')) should migrate to dependencies of 0003 but not the 0003 itself.

    opened by proofit404 9
  • Tests are skipped

    Tests are skipped

    Hi, I just wanted to give this project a go as I want to run a rather complex data migration.

    However, as soon as I add the migrator fixture to my test case it's being skipped.

    @pytest.mark.django_db
    def test_datamig(migrator):
        pass
    

    This is the test output I get:

    Test session starts (platform: linux, Python 3.8.4, pytest 5.4.3, pytest-sugar 0.9.4)
    cachedir: .pytest_cache
    django: settings: xyz.settings (from option)
    rootdir: /code, inifile: setup.cfg
    plugins: django-test-migrations-1.0.0, xdist-1.34.0, celery-4.4.7, sugar-0.9.4, django-3.9.0, mock-3.3.0, Faker-4.1.2, socket-0.3.5, cov-2.10.1, forked-1.3.0
    [gw0] linux Python 3.8.4 cwd: /code/sales_cockpit_be
    [gw0] Python 3.8.4 (default, Jul 14 2020, 02:56:59)  -- [GCC 8.3.0]
    gw0 [1]
    scheduling tests via LoadScheduling
    
    xyz/tests/test_migration.py::test_datamig s           100% β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
    [gw0] SKIPPED xyz/tests/test_migration.py 
    
    Results (14.02s):
    
           1 skipped
    

    I also tried creating migrator manually inside the test with migrator = Migrator() or migrator_factory, but the issue persists.

    I use the 1.0.0 version.

    Does any1 have an idea what the reason could be?

    opened by SaturnFromTitan 7
  • `migrator` fixture clashes with `db` fixture

    `migrator` fixture clashes with `db` fixture

    I am preloading some test data via a migration, and once my sole migration test has run, it wipes that data out and other tests fail the next time i do a test run.

    The culprit is

    @pytest.fixture()
    def migrator_factory(request, transactional_db, django_db_use_migrations):
    

    If I remove transactional_db then tests pass again

    My conftest looks like this:

    @pytest.fixture(autouse=True)
    def enable_db_access_for_all_tests(db):
        pass
    
    
    @pytest.fixture()
    def migrator():
        migrator = Migrator(database='default')
        yield migrator
        migrator.reset()
    

    My custom migrator fixture gets tests passing again.

    opened by lee-vg 7
  • integration with factory-boy or other similar solution

    integration with factory-boy or other similar solution

    Many of existing Django projects use factory-boy or other similar solution to create models instances before actual tests, so it will be really handy to provide some integration with such tools to make migrations testing easier and less verbose than Model.objects.create() etc.

    enhancement help wanted 
    opened by skarzi 7
  • Correct `post_migrate` signal muting

    Correct `post_migrate` signal muting

    This resolves #68

    It changes the behavior to muting the post_migrate signal, by clearing its receivers list, prior to the execution of the test. The receivers list remains cleared until after test cleanup, when it is restored.

    Notably only the post_migrate signal is cleared. I removed the code that cleared the pre_migrate signal as it is not fired by TransactionTestCase (and transactional_db).

    As I am not familiar with contributing to this project there might be some oddities, please feel free to point them out.

    opened by GertBurger 7
  • Extension 'ltree' already exists.

    Extension 'ltree' already exists.

    I wrote a test for the migration I am writing by hand, followed the guide for unittest and so on. Now, when I run pytest to run the actual test (this one alone) I get into the followin error:

    E               django.db.utils.ProgrammingError: extension "ltree" already exists
    

    I searched here as best as I could but could not find anything.

    Worth mentioning we run our tests with pytest and we are using the pytest-django plugin to manage django setup and so on.

    Thank you in advance for your help!

    opened by mrswats 6
  • database configuration checks

    database configuration checks

    @sobolevn I am posting this as PR, just to open some discussion about #47 implementation.

    This is initial, but unclean solution of #47, I wanted to create something extensible - something that allows us to add new checks related to database configuration really easily, that's the reason of whole django_test_migrations.db.backends module and registry for database configuration classes.

    I think after we establish how this API etc should looks like, we also need to refactor checks in our library a bit, for instance we can create some package for django checks e.g. django_test_migrations.checks and keep there all functions like autonames and django_test_migrations.db.checks etc.

    opened by skarzi 6
  • typing_extensions is still required in newer Python versions

    typing_extensions is still required in newer Python versions

    This line is in poetry.lock

    [package.dependencies]
    attrs = ">=20"
    typing_extensions = {version = "*", markers = "python_version >= \"3.7\" and python_version < \"3.8\""}
    

    However, on a fresh Ubuntu 20.04 system, after I install pytest and pytest-django, I got this error when I ran my tests:

    traceback snipped
      File "/home/vagrant/.local/share/virtualenvs/redacted-ikMrF094/lib/python3.8/site-packages/_pytest/assertion/rewrite.py", line 170, in exec_module
        exec(co, module.__dict__)
      File "/home/vagrant/.local/share/virtualenvs/redacted/lib/python3.8/site-packages/django_test_migrations/contrib/pytest_plugin.py", line 6, in <module>
        from django_test_migrations.constants import MIGRATION_TEST_MARKER
      File "/home/vagrant/.local/share/virtualenvs/redacted/lib/python3.8/site-packages/_pytest/assertion/rewrite.py", line 170, in exec_module
        exec(co, module.__dict__)
      File "/home/vagrant/.local/share/virtualenvs/redacted/lib/python3.8/site-packages/django_test_migrations/constants.py", line 1, in <module>
        from typing_extensions import Final
    ModuleNotFoundError: No module named 'typing_extensions'
    

    It would appear this module still needs to depend on typing_extensions.

    Ubuntu 20.04

    Python 3.8.6

    opened by jkugler 5
  • Inconsistent behaviour with data migration

    Inconsistent behaviour with data migration

    We added a data migration and are testing it using django-test-migrations. When running pytest with --create-db everything works fine. But when running it with --reuse-db the data added by the data migration is gone. Since this only happens when migration tests are part of the suite it seems that this is something that happens in this plugin.

    pytest 7.1.3 django-test-migrations-1.2.0

    Is there anything that could trigger this? Maybe: https://github.com/wemake-services/django-test-migrations/blob/master/django_test_migrations/migrator.py#L47

    Probably unrelated but I noticed that Migrator.reset() causes the last migration to be re-applied (after a migration test that starts with the second last and applies the last one):

    migrator.apply_initial_migration(('foo', '0001_...'))
    migrator.apply_tested_migration(('foo', '0002_...'))
    

    The call to the migrate command will cause 0002 to be re-applied. If 0002 is a data migration it will attempt to add the same data again.

    opened by mschoettle 6
  • Checks failing on MariaDB

    Checks failing on MariaDB

    In 5.7, MySQL renamed MAX_STATEMENT_TIME to be MAX_EXECUTION_TIME. MariaDB kept the old name. Due to this, when using MariaDB as database, checks fail with the following:

    django.db.utils.OperationalError: (1193, "Unknown system variable 'MAX_EXECUTION_TIME'")
    

    Is there a way that we could detect MariaDB and use a different name? πŸ€” Django doesn't differentiate between MySQL and MariaDB, sadly

    opened by kytta 1
  • fix: clear delayed apps cache of migrations project state

    fix: clear delayed apps cache of migrations project state

    I am still thinking about the test (how to write the simplest and minimal version of it), so this PR is a draft, but feel free to write some suggestions in the comments.

    Closes #292

    opened by skarzi 3
  • Models from app state aren't compatible (Cannot assign ": "B.a_fk" must be a "A" instance)">

    Models from app state aren't compatible (Cannot assign "": "B.a_fk" must be a "A" instance)

    I'm running into a problem when I apply a migration with Migrator.apply_initial_migration() and then try to construct model instances using the returned app state. One model has a foreign key to another model, but the foreign key field does not accept an instance of that model (see below for the exact output and exception).

    I tried to reduce the setup as much a possible. In the end, two additional models and 2 indexes were necessary to trigger the issue. I tested it with django-test-migrations 1.2.0 and with Django 3.2.13 and 4.0.4. The attached ZIP archive contains a complete project which reproduces the issue:

    app-state-bug.zip

    To run the test, use the following couple of commands:

    python3.10 -m venv venv
    venv/bin/pip install django~=3.2 pytest-django django-test-migrations
    venv/bin/pytest test_bad.py
    

    When running this, you should see the following output:

    ___________________________________ test_bad ___________________________________
    
    migrator = <django_test_migrations.migrator.Migrator object at 0x105c44430>
    
        def test_bad(migrator):
            state = migrator.apply_initial_migration(('my_app', '0002_foo'))
        
            A = state.apps.get_model('my_app', 'A')
            B = state.apps.get_model('my_app', 'B')
        
            print(id(A), id(B.a_fk.field.related_model))
        
    >       B.objects.create(a_fk=A.objects.create(foo=1))
    
    test_bad.py:9: 
    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
    venv/lib/python3.10/site-packages/django/db/models/manager.py:85: in manager_method
        return getattr(self.get_queryset(), name)(*args, **kwargs)
    venv/lib/python3.10/site-packages/django/db/models/query.py:512: in create
        obj = self.model(**kwargs)
    venv/lib/python3.10/site-packages/django/db/models/base.py:541: in __init__
        _setattr(self, field.name, rel_obj)
    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
    
    self = <django.db.models.fields.related_descriptors.ForwardManyToOneDescriptor object at 0x105c47520>
    instance = <B: B object (None)>, value = <A: A object (1)>
    
        def __set__(self, instance, value):
            """
            Set the related instance through the forward relation.
        
            With the example above, when setting ``child.parent = parent``:
        
            - ``self`` is the descriptor managing the ``parent`` attribute
            - ``instance`` is the ``child`` instance
            - ``value`` is the ``parent`` instance on the right of the equal sign
            """
            # An object must be an instance of the related class.
            if value is not None and not isinstance(
                value, self.field.remote_field.model._meta.concrete_model
            ):
    >           raise ValueError(
                    'Cannot assign "%r": "%s.%s" must be a "%s" instance.'
                    % (
                        value,
                        instance._meta.object_name,
                        self.field.name,
                        self.field.remote_field.model._meta.object_name,
                    )
    E               ValueError: Cannot assign "<A: A object (1)>": "B.a_fk" must be a "A" instance.
    
    venv/lib/python3.10/site-packages/django/db/models/fields/related_descriptors.py:235: ValueError
    
    opened by Feuermurmel 5
  • Can't handle squashed migrations

    Can't handle squashed migrations

    My migration is doing something like the following:

    Note: Parent and Child are both subclasses of Activity, and there is a generic relation between them (not sure if this is related to the issues I'm seeing)

    @pytest.mark.django_db
    def test_activity_migration(migrator: Migrator):
        old_state = migrator.apply_tested_migration(("app", an_old_migration_name))
        Parent = old_state.apps.get_model("app", "Parent")
        Child = old_state.apps.get_model("app", "Child")
    
        # Data setup
        parent = Parent.objects.create(...)
        Child.objects.create(..., parent=parent)
        orig_parents_values = list(Parent.objects.values())
        orig_children_values = list(Child.objects.values())
    
        # Finish all migrations on app
        state_0007 = migrator.apply_tested_migration(("app", the_newer_migration_name))
        state_0007_Parent = state_0007.apps.get_model("app", "Parent")
        state_0007_Child = state_0007.apps.get_model("app", "Child")
        state_0007_Activity = state_0007.apps.get_model("app", "Activity")
        assert models.Activity.objects.count() == 2
    
        # do not make assertion for the parent_activity_id
        orig_children_values[0].pop("parent_id")
    
        parent_0007 = state_0007_Parent.objects.values().first()
        child_0007 = state_0007_Child.objects.values().first()
    
        assert (
            state_0007_Activity.objects.filter(id=parent["activity_ptr_id"])
            .values(*orig_parents_values[0].keys())
            .first()
            == orig_parents_values[0]
        )
        assert (
            state_0007_Activity.objects.filter(id=childActivity["activity_ptr_id"])
            .values(*orig_children_values[0].keys())
            .first()
            == orig_children_values[0]
        )
    

    After squashing 2 unrelated migrations together (using manage.py squashmigrations, this test no longer runs. It gives me this traceback:

    ______________________________________________________________ ERROR at teardown of test_activity_migration ______________________________________________________________
    [snipped]
    
    /home/joel/.cache/pypoetry/virtualenvs/relate-en3S9GuS-py3.8/lib/python3.8/site-packages/django_test_migrations/migrator.py:76: 
    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
    /home/joel/.cache/pypoetry/virtualenvs/relate-en3S9GuS-py3.8/lib/python3.8/site-packages/django/core/management/__init__.py:181: in call_command
        return command.execute(*args, **defaults)
    /home/joel/.cache/pypoetry/virtualenvs/relate-en3S9GuS-py3.8/lib/python3.8/site-packages/django/core/management/base.py:398: in execute
        output = self.handle(*args, **options)
    /home/joel/.cache/pypoetry/virtualenvs/relate-en3S9GuS-py3.8/lib/python3.8/site-packages/django/core/management/base.py:89: in wrapped
        res = handle_func(*args, **kwargs)
    /home/joel/.cache/pypoetry/virtualenvs/relate-en3S9GuS-py3.8/lib/python3.8/site-packages/django/core/management/commands/migrate.py:95: in handle
        executor.loader.check_consistent_history(connection)
    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
    
    [snipped]
    E                   django.db.migrations.exceptions.InconsistentMigrationHistory: Migration app.0015_squashed_migration_name is applied before its dependency app.0014_previous_migration_name on database 'default'.
    
    /home/joel/.cache/pypoetry/virtualenvs/relate-en3S9GuS-py3.8/lib/python3.8/site-packages/django/db/migrations/loader.py:306: InconsistentMigrationHistory
    ___________________________________________________________ ERROR at teardown of test_automatic_child_activity ___________________________________________________________
    
    [snipped]
    
    /home/joel/.cache/pypoetry/virtualenvs/relate-en3S9GuS-py3.8/lib/python3.8/site-packages/django_test_migrations/migrator.py:76: 
    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
    /home/joel/.cache/pypoetry/virtualenvs/relate-en3S9GuS-py3.8/lib/python3.8/site-packages/django/core/management/__init__.py:181: in call_command
        return command.execute(*args, **defaults)
    /home/joel/.cache/pypoetry/virtualenvs/relate-en3S9GuS-py3.8/lib/python3.8/site-packages/django/core/management/base.py:398: in execute
        output = self.handle(*args, **options)
    /home/joel/.cache/pypoetry/virtualenvs/relate-en3S9GuS-py3.8/lib/python3.8/site-packages/django/core/management/base.py:89: in wrapped
        res = handle_func(*args, **kwargs)
    /home/joel/.cache/pypoetry/virtualenvs/relate-en3S9GuS-py3.8/lib/python3.8/site-packages/django/core/management/commands/migrate.py:95: in handle
        executor.loader.check_consistent_history(connection)
    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
    
    self = <django.db.migrations.loader.MigrationLoader object at 0x7f808bbd0fd0>, connection = <django.db.backends.postgresql.base.DatabaseWrapper object at 0x7f8091eaaa30>
    
    [snipped]
    E                   django.db.migrations.exceptions.InconsistentMigrationHistory: Migration app.0015_squashed_migration_name is applied before its dependency app.0014_previous_migration_name on database 'default'.
    
    /home/joel/.cache/pypoetry/virtualenvs/relate-en3S9GuS-py3.8/lib/python3.8/site-packages/django/db/migrations/loader.py:306: InconsistentMigrationHistory
    

    After this there is a second failure on the same test, related to the migrations not having applied successfully.

    opened by ukch 1
  • use run_syncdb in reset

    use run_syncdb in reset

    In my project I have two databases. When testing a migration I got the following error:

    Database queries to 'secondary' are not allowed in this test. Add 'secondary' to pytest_django.fixtures._django_db_helper.<locals>.PytestDjangoTestCase.databases to ensure proper test isolation and silence this failure.
    

    This was odd because the test did not use the secondary database at all. By inspecting the stack trace I found that migrator.reset() executes all migrations, and there is actually an unrelated migration that uses the secondary database.

    I think migrator.reset() should not execute all migrations. Therefore it seems like a good idea to use the --run-syncdb option. (I am not perfectly sure whether I got the syntax right)

    opened by xi 2
Releases(1.2.0)
  • 1.2.0(Dec 13, 2021)

  • 1.1.0(Dec 23, 2020)

    Features

    • Adds Django 3.1 support (#123, #154)
    • Adds markers/tags to migration tests (#138)
    • Adds database configuration checks (#91)

    Bugfixes

    • Fixes tables dropping on MySQL by disabling foreign keys checks (#149)
    • Fixes migrate signals muting when running migrations tests (#133)

    Misc

    • Runs tests against PostgreSQL and MySQL database engines (#129)
    Source code(tar.gz)
    Source code(zip)
  • 1.0.0(May 30, 2020)

    Breaking Changes

    • Rename following Migrator methods (#83):

      • before to apply_initial_migration
      • after to apply_tested_migration
    • Improves databases setup and teardown for migrations tests (#76) Currently Migrator.reset uses migrate management command and all logic related to migrations tests setup is moved to Migrator.apply_tested_migration.

    Bugfixes

    • Fixes pre_migrate and post_migrate signals muting (#87)
    • Adds missing typing_extension dependency (#86)

    Misc

    • Refactor tests (#79)
    • Return django installed from master branch to testing matrix (#77)
    Source code(tar.gz)
    Source code(zip)
  • 0.3.0(May 8, 2020)

    Features

    • Drops [email protected] support
    • Adds '*' alias for ignoring all migrations in an app with DTM_IGNORED_MIGRATIONS

    Bugfixes

    • Fixes how pre_migrate and post_migrate signals are muted

    Misc

    • Updates wemake-python-styleguide
    • Moves from travis to Github Actions
    Source code(tar.gz)
    Source code(zip)
  • 0.2.0(Feb 25, 2020)

    Features

    • Adds autoname check to forbid *_auto_* named migrations
    • Adds [email protected] support
    • Adds python3.8 support

    Bugfixes

    • Fixes that migtaions were failing with pre_migrate and post_migrate signals
    • Fixes that tests were failing when pytest --nomigration was executed, now they are skipped

    Misc

    Source code(tar.gz)
    Source code(zip)
  • 0.1.0(Nov 21, 2019)

Pytest plugin for testing the idempotency of a function.

pytest-idempotent Pytest plugin for testing the idempotency of a function. Usage pip install pytest-idempotent Documentation Suppose we had the follo

Tyler Yep 3 Dec 14, 2022
A set of pytest fixtures to test Flask applications

pytest-flask An extension of pytest test runner which provides a set of useful tools to simplify testing and development of the Flask extensions and a

pytest-dev 433 Dec 23, 2022
Sixpack is a language-agnostic a/b-testing framework

Sixpack Sixpack is a framework to enable A/B testing across multiple programming languages. It does this by exposing a simple API for client libraries

1.7k Dec 24, 2022
Getting the most out of your hobby servo

ServoProject by Adam BΓ€ckstrΓΆm Getting the most out of your hobby servo Theory The control system of a regular hobby servo looks something like this:

209 Dec 20, 2022
A simple serverless create api test repository. Please Ignore.

serverless-create-api-test A simple serverless create api test repository. Please Ignore. Things to remember: Setup workflow Change Name in workflow e

Sarvesh Bhatnagar 1 Jan 18, 2022
hyppo is an open-source software package for multivariate hypothesis testing.

hyppo (HYPothesis Testing in PythOn, pronounced "Hippo") is an open-source software package for multivariate hypothesis testing.

neurodata 137 Dec 18, 2022
Python version of the Playwright testing and automation library.

🎭 Playwright for Python Docs | API Playwright is a Python library to automate Chromium, Firefox and WebKit browsers with a single API. Playwright del

Microsoft 7.8k Jan 02, 2023
An improbable web debugger through WebSockets

wdb - Web Debugger Description wdb is a full featured web debugger based on a client-server architecture. The wdb server which is responsible of manag

Kozea 1.6k Dec 09, 2022
Test utility for validating OpenAPI documentation

DRF OpenAPI Tester This is a test utility to validate DRF Test Responses against OpenAPI 2 and 3 schema. It has built-in support for: OpenAPI 2/3 yaml

snok 103 Dec 21, 2022
Code coverage measurement for Python

Coverage.py Code coverage testing for Python. Coverage.py measures code coverage, typically during test execution. It uses the code analysis tools and

Ned Batchelder 2.3k Jan 04, 2023
A pytest plugin to skip `@pytest.mark.slow` tests by default.

pytest-skip-slow A pytest plugin to skip @pytest.mark.slow tests by default. Include the slow tests with --slow. Installation $ pip install pytest-ski

Brian Okken 19 Jan 04, 2023
Front End Test Automation with Pytest Framework

Front End Test Automation Framework with Pytest Installation and running instructions: 1. To install the framework on your local machine: clone the re

Sergey Kolokolov 2 Jun 17, 2022
Python script to automatically download from Zippyshare

Zippyshare downloader and Links Extractor Python script to automatically download from Zippyshare using Selenium package and Internet Download Manager

Daksh Khurana 2 Oct 31, 2022
A testing system for catching visual regressions in Web applications.

Huxley Watches you browse, takes screenshots, tells you when they change Huxley is a test-like system for catching visual regressions in Web applicati

Facebook Archive 4.1k Nov 30, 2022
No longer maintained, please migrate to model_bakery

Model Mommy: Smart fixtures for better tests IMPORTANT: Model Mommy is no longer maintained and was replaced by Model Bakery. Please, consider migrati

Bernardo Fontes 917 Oct 04, 2022
a socket mock framework - for all kinds of socket animals, web-clients included

mocket /mΙ”ΛˆkΙ›t/ A socket mock framework for all kinds of socket animals, web-clients included - with gevent/asyncio/SSL support ...and then MicroPytho

Giorgio Salluzzo 249 Dec 14, 2022
An Instagram bot that can mass text users, receive and read a text, and store it somewhere with user details.

Instagram Bot πŸ€– July 14, 2021 Overview πŸ‘ A multifunctionality automated instagram bot that can mass text users, receive and read a message and store

Abhilash Datta 14 Dec 06, 2022
Show, Edit and Tell: A Framework for Editing Image Captions, CVPR 2020

Show, Edit and Tell: A Framework for Editing Image Captions | arXiv This contains the source code for Show, Edit and Tell: A Framework for Editing Ima

Fawaz Sammani 76 Nov 25, 2022
This is a simple software for fetching new changes to remote repositories automatically.

Git Autofetch Git Autofetch is a simple software for fetching new changes from a repo to local repositories after a set time interval. This program is

Shreyas Ashtamkar 10 Jul 21, 2022
Integration layer between Requests and Selenium for automation of web actions.

Requestium is a Python library that merges the power of Requests, Selenium, and Parsel into a single integrated tool for automatizing web actions. The

Tryolabs 1.7k Dec 27, 2022