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)

Aioresponses is a helper for mock/fake web requests in python aiohttp package.

aioresponses Aioresponses is a helper to mock/fake web requests in python aiohttp package. For requests module there are a lot of packages that help u

402 Jan 06, 2023
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
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
Minimal example of getting Django + PyTest running on GitHub Actions

Minimal Django + Pytest + GitHub Actions example This minimal example shows you how you can runs pytest on your Django app on every commit using GitHu

Matt Segal 5 Sep 19, 2022
BDD library for the py.test runner

BDD library for the py.test runner pytest-bdd implements a subset of the Gherkin language to enable automating project requirements testing and to fac

pytest-dev 1.1k Jan 09, 2023
DUCKSPLOIT - Windows Hacking FrameWork using Reverse Shell

Ducksploit Install Ducksploit Hacker setup raspberry pico Download https://githu

2 Jan 31, 2022
A Proof of concept of a modern python CLI with click, pydantic, rich and anyio

httpcli This project is a proof of concept of a modern python networking cli which can be simple and easy to maintain using some of the best packages

Kevin Tewouda 17 Nov 15, 2022
Auto-hms-action - Automation of NU Health Management System

🦾 Automation of NU Health Management System 🤖 長崎大学 健康管理システムの自動化 🏯 Usage / 使い方

k5-mot 3 Mar 04, 2022
Data App Performance Tests

Data App Performance Tests My hypothesis is that The different architectures of

Marc Skov Madsen 6 Dec 14, 2022
模仿 USTC CAS 的程序,用于开发校内网站应用的本地调试。

ustc-cas-mock 模仿 USTC CAS 的程序,用于开发校内网站应用阶段调试。 请勿在生产环境部署! 只测试了最常用的三个 CAS route: /login /serviceValidate(验证 CAS ticket) /logout 没有测试过 proxy ticket。(因为我

taoky 4 Jan 27, 2022
A Simple Unit Test Matcher Library for Python 3

pychoir - Python Test Matchers for humans Super duper low cognitive overhead matching for Python developers reading or writing tests. Implemented in p

Antti Kajander 15 Sep 14, 2022
Python selenium script to bypass simaster.ugm.ac.id weak captcha.

Python selenium script to bypass simaster.ugm.ac.id weak "captcha".

Hafidh R K 1 Feb 01, 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
py.test fixture for benchmarking code

Overview docs tests package A pytest fixture for benchmarking code. It will group the tests into rounds that are calibrated to the chosen timer. See c

Ionel Cristian Mărieș 1k Jan 03, 2023
Rerun pytest when your code changes

A simple watcher for pytest Overview pytest-watcher is a tool to automatically rerun pytest when your code changes. It looks for the following events:

Olzhas Arystanov 74 Dec 29, 2022
pytest plugin for testing mypy types, stubs, and plugins

pytest plugin for testing mypy types, stubs, and plugins Installation This package is available on PyPI pip install pytest-mypy-plugins and conda-forg

TypedDjango 74 Dec 31, 2022
Show coverage stats online via coveralls.io

Coveralls for Python Test Status: Version Info: Compatibility: Misc: coveralls.io is a service for publishing your coverage stats online. This package

Kevin James 499 Dec 28, 2022
A Modular Penetration Testing Framework

fsociety A Modular Penetration Testing Framework Install pip install fsociety Update pip install --upgrade fsociety Usage usage: fsociety [-h] [-i] [-

fsociety-team 802 Dec 31, 2022
Free cleverbot without headless browser

Cleverbot Scraper Simple free cleverbot library that doesn't require running a heavy ram wasting headless web browser to actually chat with the bot, a

Matheus Fillipe 3 Sep 25, 2022
Scalable user load testing tool written in Python

Locust Locust is an easy to use, scriptable and scalable performance testing tool. You define the behaviour of your users in regular Python code, inst

Locust.io 20.4k Jan 04, 2023