-
Notifications
You must be signed in to change notification settings - Fork 126
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
DX: Remove db-diff dependency #288
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,6 @@ | ||
Unreleased | ||
Remove db-diff dependency | ||
|
||
2023-10-30 | ||
Add support for Python 3.12 | ||
Add support for Django 5.0 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,10 @@ | ||
from django.apps import AppConfig | ||
from django.core.serializers import register_serializer | ||
|
||
|
||
class CitiesLightConfig(AppConfig): | ||
default_auto_field = 'django.db.models.AutoField' | ||
name = 'cities_light' | ||
|
||
def ready(self): | ||
register_serializer('sorted_json', 'cities_light.serializers.json') | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
"""Serializers with predictible (ordered) output.""" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
"""Shared code for serializers.""" | ||
|
||
import collections | ||
import datetime | ||
import decimal | ||
|
||
|
||
class BaseSerializerMixin(object): | ||
"""Serializer mixin for predictible and cross-db dumps.""" | ||
|
||
@classmethod | ||
def recursive_dict_sort(cls, data): | ||
""" | ||
Return a recursive OrderedDict for a dict. | ||
|
||
Django's default model-to-dict logic - implemented in | ||
django.core.serializers.python.Serializer.get_dump_object() - returns a | ||
dict, this app registers a slightly modified version of the default | ||
json serializer which returns OrderedDicts instead. | ||
""" | ||
ordered_data = collections.OrderedDict(sorted(data.items())) | ||
|
||
for key, value in ordered_data.items(): | ||
if isinstance(value, dict): | ||
ordered_data[key] = cls.recursive_dict_sort(value) | ||
|
||
return ordered_data | ||
|
||
@classmethod | ||
def remove_microseconds(cls, data): | ||
""" | ||
Strip microseconds from datetimes for mysql. | ||
|
||
MySQL doesn't have microseconds in datetimes, so dbdiff's serializer | ||
removes microseconds from datetimes so that fixtures are cross-database | ||
compatible which make them usable for cross-database testing. | ||
""" | ||
for key, value in data['fields'].items(): | ||
if not isinstance(value, datetime.datetime): | ||
continue | ||
|
||
data['fields'][key] = datetime.datetime( | ||
year=value.year, | ||
month=value.month, | ||
day=value.day, | ||
hour=value.hour, | ||
minute=value.minute, | ||
second=value.second, | ||
tzinfo=value.tzinfo | ||
) | ||
|
||
@classmethod | ||
def normalize_decimals(cls, data): | ||
""" | ||
Strip trailing zeros for constitency. | ||
|
||
In addition, dbdiff serialization forces Decimal normalization, because | ||
trailing zeros could happen in inconsistent ways. | ||
""" | ||
for key, value in data['fields'].items(): | ||
if not isinstance(value, decimal.Decimal): | ||
continue | ||
|
||
if value % 1 == 0: | ||
data['fields'][key] = int(value) | ||
else: | ||
print(type(value), " => ", type(value.normalize())) | ||
print(value, " => ", value.normalize()) | ||
# data['fields'][key] = value | ||
data['fields'][key] = value.normalize() | ||
|
||
def get_dump_object(self, obj): | ||
""" | ||
Actual method used by Django serializers to dump dicts. | ||
|
||
By overridding this method, we're able to run our various | ||
data dump predictability methods. | ||
""" | ||
data = super(BaseSerializerMixin, self).get_dump_object(obj) | ||
self.remove_microseconds(data) | ||
self.normalize_decimals(data) | ||
data = self.recursive_dict_sort(data) | ||
return data |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
"""Django JSON Serializer override.""" | ||
|
||
from django.core.serializers import json as upstream | ||
|
||
from .base import BaseSerializerMixin | ||
|
||
|
||
__all__ = ('Serializer', 'Deserializer') | ||
|
||
|
||
class Serializer(BaseSerializerMixin, upstream.Serializer): | ||
"""Sorted dict JSON serializer.""" | ||
|
||
|
||
Deserializer = upstream.Deserializer |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,6 +29,21 @@ | |
"model": "cities_light.region", | ||
"pk": 1 | ||
}, | ||
{ | ||
"fields": { | ||
"alternate_names": "Юргинский район", | ||
"country": [2017370], | ||
"display_name": "Yurginskiy Rayon, Russia", | ||
"geoname_code": "1485714", | ||
"geoname_id": 1485714, | ||
"name": "Yurginskiy Rayon", | ||
"name_ascii": "Yurginskiy Rayon", | ||
"region": [1503900], | ||
"slug": "yurginskiy-rayon" | ||
}, | ||
"model": "cities_light.subregion", | ||
"pk": 1 | ||
}, | ||
Comment on lines
+32
to
+46
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I noticed that this fixture wasn't up to date :/ |
||
{ | ||
"fields": { | ||
"alternate_names": "\u041a\u0435\u043c\u0435\u0440\u043e\u0432\u043e", | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,31 +1,27 @@ | ||
import unittest | ||
from io import StringIO | ||
|
||
from django import test | ||
from django.apps import apps | ||
from django.db.migrations.autodetector import MigrationAutodetector | ||
from django.db.migrations.loader import MigrationLoader | ||
from django.db.migrations.questioner import ( | ||
InteractiveMigrationQuestioner, ) | ||
from django.db.migrations.state import ProjectState | ||
from django.core.management import call_command | ||
from django.test.utils import override_settings | ||
|
||
|
||
@override_settings( | ||
MIGRATION_MODULES={ | ||
"cities_light": "cities_light.migrations", | ||
}, | ||
) | ||
class TestNoMigrationLeft(test.TestCase): | ||
@unittest.skip("TODO: make the test pass") | ||
def test_no_migration_left(self): | ||
loader = MigrationLoader(None, ignore_no_migrations=True) | ||
conflicts = loader.detect_conflicts() | ||
app_labels = ['cities_light'] | ||
|
||
autodetector = MigrationAutodetector( | ||
loader.project_state(), | ||
ProjectState.from_apps(apps), | ||
InteractiveMigrationQuestioner(specified_apps=app_labels, dry_run=True), | ||
) | ||
|
||
changes = autodetector.changes( | ||
graph=loader.graph, | ||
trim_to_apps=app_labels or None, | ||
convert_apps=app_labels or None, | ||
) | ||
|
||
assert 'cities_light' not in changes | ||
out = StringIO() | ||
try: | ||
call_command( | ||
"makemigrations", | ||
"cities_light", | ||
"--dry-run", | ||
"--check", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Call the real makemigrations command instead of the MigrationLoader. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like it, and some other modifications. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice, I will try to move forward and get a stable state. |
||
stdout=out, | ||
stderr=StringIO(), | ||
) | ||
except SystemExit: # pragma: no cover | ||
raise AssertionError("Pending migrations:\n" + out.getvalue()) from None |
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.
Register an additional serializer where the dbdiff package was overriding the default json serializer.