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)

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
Declarative HTTP Testing for Python and anything else

Gabbi Release Notes Gabbi is a tool for running HTTP tests where requests and responses are represented in a declarative YAML-based form. The simplest

Chris Dent 139 Sep 21, 2022
Donors data of Tamil Nadu Chief Ministers Relief Fund scrapped from https://ereceipt.tn.gov.in/cmprf/Interface/CMPRF/MonthWiseReport

Tamil Nadu Chief Minister's Relief Fund Donors Scrapped data from https://ereceipt.tn.gov.in/cmprf/Interface/CMPRF/MonthWiseReport Scrapper scrapper.p

Arunmozhi 5 May 18, 2021
Baseball Discord bot that can post up-to-date scores, lineups, and home runs.

Sunny Day Discord Bot Baseball Discord bot that can post up-to-date scores, lineups, and home runs. Uses webscraping techniques to scrape baseball dat

Benjamin Hammack 1 Jun 20, 2022
CNE-OVS-SIT - OVS System Integration Test Suite

CNE-OVS-SIT - OVS System Integration Test Suite Introduction User guide Discussion Introduction CNE-OVS-SIT is a test suite for OVS end-to-end functio

4 Jan 09, 2022
masscan + nmap 快速端口存活检测和服务识别

masnmap masscan + nmap 快速端口存活检测和服务识别。 思路很简单,将masscan在端口探测的高速和nmap服务探测的准确性结合起来,达到一种相对比较理想的效果。 先使用masscan以较高速率对ip存活端口进行探测,再以多进程的方式,使用nmap对开放的端口进行服务探测。 安

starnightcyber 75 Dec 19, 2022
Based on the selenium automatic test framework of python, the program crawls the score information of the educational administration system of a unive

whpu_spider 该程序基于python的selenium自动化测试框架,对某高校的教务系统的成绩信息实时爬取,在检测到成绩更新之后,会通过电子邮件的方式,将更新的成绩以文本的方式发送给用户,可以使得用户在不必手动登录教务系统网站时,实时获取成绩更新的信息。 该程序仅供学习交流,不可用于恶意攻

1 Dec 30, 2021
pytest plugin to test mypy static type analysis

pytest-mypy-testing — Plugin to test mypy output with pytest pytest-mypy-testing provides a pytest plugin to test that mypy produces a given output. A

David Fritzsche 21 Dec 21, 2022
0hh1 solver for the web (selenium) and also for mobile (adb)

0hh1 - Solver Aims to solve the '0hh1 puzzle' for all the sizes (4x4, 6x6, 8x8, 10x10 12x12). for both the web version (using selenium) and on android

Adwaith Rajesh 1 Nov 05, 2021
This repository contains a testing script for nmigen-boards that tries to build blinky for all the platforms provided by nmigen-boards.

Introduction This repository contains a testing script for nmigen-boards that tries to build blinky for all the platforms provided by nmigen-boards.

S.J.R. van Schaik 4 Jul 23, 2022
Python Moonlight (Machine Learning) Practice

PyML Python Moonlight (Machine Learning) Practice Contents Design Documentation Prerequisites Checklist Dev Setup Testing Run Prerequisites Python 3 P

Dockerian Seattle 2 Dec 25, 2022
XSSearch - A comprehensive reflected XSS tool built on selenium framework in python

XSSearch A Comprehensive Reflected XSS Scanner XSSearch is a comprehensive refle

Sathyaprakash Sahoo 49 Oct 18, 2022
Android automation project with pytest+appium

Android automation project with pytest+appium

1 Oct 28, 2021
Argument matchers for unittest.mock

callee Argument matchers for unittest.mock More robust tests Python's mocking library (or its backport for Python 3.3) is simple, reliable, and easy

Karol Kuczmarski 77 Nov 03, 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
A cross-platform GUI automation Python module for human beings. Used to programmatically control the mouse & keyboard.

PyAutoGUI PyAutoGUI is a cross-platform GUI automation Python module for human beings. Used to programmatically control the mouse & keyboard. pip inst

Al Sweigart 7.5k Dec 31, 2022
pytest plugin for distributed testing and loop-on-failures testing modes.

xdist: pytest distributed testing plugin The pytest-xdist plugin extends pytest with some unique test execution modes: test run parallelization: if yo

pytest-dev 1.1k Dec 30, 2022
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
pytest splinter and selenium integration for anyone interested in browser interaction in tests

Splinter plugin for the pytest runner Install pytest-splinter pip install pytest-splinter Features The plugin provides a set of fixtures to use splin

pytest-dev 238 Nov 14, 2022
Automates hiketop+ crystal earning using python and appium

hikepy Works on poco x3 idk about your device deponds on resolution Prerquests Android sdk java adb Setup Go to https://appium.io/ Download and instal

4 Aug 26, 2022