-
-
Notifications
You must be signed in to change notification settings - Fork 30
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix and refactor migrations test teardown and setup #76
Merged
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
63ed305
Revert "refactor database reset on test's teardown (#42)"
skarzi 5812cef
always reset database being tested instead of default
skarzi 994bfd4
add types modules for common typing aliases etc
skarzi fd50fb5
show missing lines in coverage report
skarzi 0210cb7
extract migration target normalization to function
skarzi 276f1a9
update migrations test setup to migrate only forward
skarzi c3d0e6c
apply code review suggestions
skarzi File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
from django_test_migrations.types import MigrationTarget | ||
|
||
|
||
class MigrationNotInPlan(Exception): | ||
"""``MigrationTarget`` not found in migrations plan.""" | ||
|
||
def __init__(self, migration_target: MigrationTarget) -> None: # noqa: D107 | ||
self.migration_target = migration_target | ||
|
||
def __str__(self) -> str: | ||
"""String representation of exception's instance.""" | ||
return 'Migration {0} not found in migrations plan'.format( | ||
self.migration_target, | ||
) |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
from typing import List | ||
|
||
from django_test_migrations.types import MigrationSpec, MigrationTarget | ||
|
||
|
||
def normalize(migration_target: MigrationSpec) -> List[MigrationTarget]: | ||
"""Normalize ``migration_target`` to expected format.""" | ||
if not isinstance(migration_target, list): | ||
migration_target = [migration_target] | ||
return migration_target |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
from functools import partial | ||
from typing import Callable, Dict, List, Optional, Union | ||
|
||
from django.core.management.color import Style, no_style | ||
from django.db import DefaultConnectionProxy, connections, transaction | ||
from django.db.backends.base.base import BaseDatabaseWrapper | ||
from typing_extensions import Final | ||
|
||
_Connection = Union[DefaultConnectionProxy, BaseDatabaseWrapper] | ||
|
||
DJANGO_MIGRATIONS_TABLE_NAME: Final = 'django_migrations' | ||
|
||
|
||
def drop_models_tables( | ||
database_name: str, | ||
style: Optional[Style] = None, | ||
) -> None: | ||
"""Drop all installed Django's models tables.""" | ||
style = style or no_style() | ||
connection = connections[database_name] | ||
tables = connection.introspection.django_table_names( | ||
only_existing=True, | ||
include_views=False, | ||
) | ||
sql_drop_tables = [ | ||
connection.SchemaEditorClass.sql_delete_table % { | ||
'table': style.SQL_FIELD(connection.ops.quote_name(table)), | ||
} | ||
for table in tables | ||
] | ||
if sql_drop_tables: | ||
get_execute_sql_flush_for(connection)(database_name, sql_drop_tables) | ||
|
||
|
||
def flush_django_migrations_table( | ||
database_name: str, | ||
style: Optional[Style] = None, | ||
) -> None: | ||
"""Flush `django_migrations` table. | ||
|
||
Ensures compability with all supported Django versions. | ||
`django_migrations` is not "regular" Django model, so its not returned | ||
by ``ConnectionRouter.get_migratable_models`` which is used e.g. to | ||
implement sequences reset in ``Django==1.11``. | ||
|
||
""" | ||
style = style or no_style() | ||
connection = connections[database_name] | ||
django_migrations_sequences = get_django_migrations_table_sequences( | ||
connection, | ||
) | ||
execute_sql_flush = get_execute_sql_flush_for(connection) | ||
execute_sql_flush( | ||
database_name, | ||
connection.ops.sql_flush( | ||
style, | ||
[DJANGO_MIGRATIONS_TABLE_NAME], | ||
django_migrations_sequences, | ||
allow_cascade=False, | ||
), | ||
) | ||
|
||
|
||
def get_django_migrations_table_sequences( | ||
connection: _Connection, | ||
) -> List[Dict[str, str]]: | ||
"""`django_migrations` table introspected sequences. | ||
|
||
Returns properly inspected sequences when using ``Django>1.11`` | ||
and static sequence for `id` column otherwise. | ||
|
||
""" | ||
if hasattr(connection.introspection, 'get_sequences'): # noqa: WPS421 | ||
with connection.cursor() as cursor: | ||
return connection.introspection.get_sequences( | ||
cursor, | ||
DJANGO_MIGRATIONS_TABLE_NAME, | ||
) | ||
# for ``Django==1.11`` only primary key sequence is returned | ||
return [{'table': DJANGO_MIGRATIONS_TABLE_NAME, 'column': 'id'}] | ||
|
||
|
||
def get_execute_sql_flush_for( | ||
connection: _Connection, | ||
) -> Callable[[str, List[str]], None]: | ||
"""Return ``execute_sql_flush`` callable for given connection.""" | ||
return getattr( | ||
connection.ops, | ||
'execute_sql_flush', | ||
partial(execute_sql_flush, connection), | ||
) | ||
|
||
|
||
def execute_sql_flush( | ||
connection: _Connection, | ||
using: str, | ||
sql_list: List[str], | ||
) -> None: # pragma: no cover | ||
"""Execute a list of SQL statements to flush the database. | ||
|
||
This function is copy of ``connection.ops.execute_sql_flush`` | ||
method from Django's source code: https://bit.ly/3doGMye | ||
to make `django-test-migrations` compatible with ``Django==1.11``. | ||
``connection.ops.execute_sql_flush()`` was introduced in ``Django==2.0``. | ||
|
||
""" | ||
with transaction.atomic( | ||
using=using, | ||
savepoint=connection.features.can_rollback_ddl, | ||
): | ||
with connection.cursor() as cursor: | ||
for sql in sql_list: | ||
cursor.execute(sql) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
from typing import List, Optional, Tuple, Union | ||
|
||
from django.db.migrations import Migration | ||
|
||
# Migration target: (app_name, migration_name) | ||
# Regular or rollback migration: 0001 -> 0002, or 0002 -> 0001 | ||
# Rollback migration to initial state: 0001 -> None | ||
MigrationTarget = Tuple[str, Optional[str]] | ||
MigrationSpec = Union[MigrationTarget, List[MigrationTarget]] | ||
|
||
MigrationPlan = List[Tuple[Migration, bool]] |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this operation safe in terms of sql-injection? Maybe there are safer methods to join sql vars?
Should we even care?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should be safe, because
sql_delete_table
is string format defined by database backend (no user input) and itstable
argument is table's name (not any users input again), so it should be save.This implementation is based on https://github.com/django/django/blob/c226c6cb3209122b6732fd501e2994c408dc258e/django/db/backends/base/schema.py#L342
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍