From 3ce3fafec9bdf2a1f06dac0763fe8d153b5527d9 Mon Sep 17 00:00:00 2001 From: Joseph Kahn Date: Sun, 10 Sep 2017 15:29:08 -0400 Subject: [PATCH] 2.0.0: Some initial support for deleting models and unpin libs (#67) * slightly better for now * WIP * update travil.yml * precise * update version and add tests for deleted models * fixed and added test coverage * update to 2.0.0 --- .travis.yml | 23 +++++++------------ CHANGES.md | 12 ++++++++++ MANIFEST.in | 1 + django_sharding_library/router.py | 25 +++++++++++++++----- requirements/_without_django.txt | 1 - requirements/common.txt | 3 +-- requirements/development.txt | 9 ++++---- setup.py | 14 ++++++++---- tests/test_router.py | 38 ++++++++++++++++++++++++++++++- tox.ini | 9 +++----- 10 files changed, 95 insertions(+), 40 deletions(-) delete mode 100644 requirements/_without_django.txt diff --git a/.travis.yml b/.travis.yml index 27b454a..2433dd9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,8 @@ sudo: false language: python -python: 3.5 +python: 3.6 cache: pip +dist: precise services: - mysql - postgresql @@ -21,23 +22,15 @@ env: - TOX_ENV=py27-dj19 - TOX_ENV=py27-dj110 - TOX_ENV=py27-dj111 - - TOX_ENV=py27-djdev - - TOX_ENV=py34-dj18 - - TOX_ENV=py34-dj19 - - TOX_ENV=py34-dj110 - - TOX_ENV=py34-dj111 - - TOX_ENV=py34-djdev - - TOX_ENV=py35-dj18 - - TOX_ENV=py35-dj19 - - TOX_ENV=py35-dj110 - - TOX_ENV=py35-dj111 - - TOX_ENV=py35-djdev + - TOX_ENV=py36-dj18 + - TOX_ENV=py36-dj19 + - TOX_ENV=py36-dj110 + - TOX_ENV=py36-dj111 + - TOX_ENV=py36-djdev matrix: fast_finish: true allow_failures: - - env: TOX_ENV=py27-djdev - - env: TOX_ENV=py34-djdev - - env: TOX_ENV=py35-djdev + - env: TOX_ENV=py36-djdev before_install: - psql -c 'CREATE DATABASE "default";' -U postgres diff --git a/CHANGES.md b/CHANGES.md index 6044553..053c332 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,18 @@ Changelog ========= +2.0.0 (Sep 10th 2017) + +### Small updates + +- Unpin `dj-database-url` from a specific minor version to a specific major version. +- Initial support for deleting django models. I'm not going to update the docs just yet +as I don't entirely like this solution...something is better than nothing. +Check the `test_deleted_model_in_settings` tests to see this. The issue is that django +needs to know about a model after you've deleted the class so sharded settings on deleted +models need to be tracked somewhere. + + 1.2.0 (May 1st 2017) ------------------ diff --git a/MANIFEST.in b/MANIFEST.in index bb19260..f076118 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,2 +1,3 @@ include *.md include requirements.txt +include requirements/*.txt diff --git a/django_sharding_library/router.py b/django_sharding_library/router.py index 7bf7486..b38642e 100644 --- a/django_sharding_library/router.py +++ b/django_sharding_library/router.py @@ -143,12 +143,25 @@ def allow_migrate(self, db, app_label, model_name=None, **hints): # is the app label of the app where the change is defined but to app with the model is # passed in with the model name. try: - app = apps.get_app_config(app_label) - model = app.get_model(model_name) - except LookupError: - app_label = model_name.split('.')[0] - app = apps.get_app_config(app_label) - model = app.get_model(model_name[len(app_label) + 1:]) + if "." in model_name: + _app_label = model_name.split('.')[0] + app = apps.get_app_config(_app_label) + model = app.get_model(model_name[len(_app_label) + 1:]) + else: + app = apps.get_app_config(app_label) + model = app.get_model(model_name) + except LookupError as exception: + deleted_model_settings = getattr(settings, 'DJANGO_SHARDING_SETTINGS', {}).get('DELETED_MODELS', {}) + entry = "{}.{}".format(app_label, model_name) if "." not in model_name else model_name + if entry not in deleted_model_settings: + raise exception + deleted_model_data = deleted_model_settings[entry] + if deleted_model_data is None or ("shard_group" not in deleted_model_data and "database" not in deleted_model_data): + return db == "default" + elif "database" in deleted_model_data: + return db == deleted_model_data["database"] + else: + return settings.DATABASES[db]['SHARD_GROUP'] == deleted_model_data["shard_group"] try: return is_model_class_on_database(model=model, database=db) diff --git a/requirements/_without_django.txt b/requirements/_without_django.txt deleted file mode 100644 index 4614e0b..0000000 --- a/requirements/_without_django.txt +++ /dev/null @@ -1 +0,0 @@ -dj-database-url==0.4.1 diff --git a/requirements/common.txt b/requirements/common.txt index 620c89e..9888f20 100644 --- a/requirements/common.txt +++ b/requirements/common.txt @@ -1,2 +1 @@ -django>=1.8 --r _without_django.txt +dj-database-url>=0.4.1,<1.0.0 diff --git a/requirements/development.txt b/requirements/development.txt index 5c0d5ae..b34b0b2 100644 --- a/requirements/development.txt +++ b/requirements/development.txt @@ -1,5 +1,4 @@ -psycopg2==2.6.2 -mock==2.0.0 -django_nose==1.4.4 -mysqlclient==1.3.7 -dj-database-url==0.4.1 +psycopg2>=2.6.2 +mock>=2.0.0 +django_nose>=1.4.4 +mysqlclient>=1.3.7 diff --git a/setup.py b/setup.py index 78e72b6..5508cea 100755 --- a/setup.py +++ b/setup.py @@ -1,6 +1,11 @@ from setuptools import setup, find_packages -version = '1.2.0' +version = '2.0.0' + + +def get_requirements(file_path): + with open(file_path) as f: + return [line for line in f if line and not line.startswith('-')] setup( @@ -13,8 +18,8 @@ url='https://github.com/JBKahn/django-sharding', packages=find_packages(), include_package_data=True, - install_requires=['Django>=1.8', 'dj-database-url==0.4.1'], - tests_require=['psycopg2==2.6.2', 'mysqlclient==1.3.7', 'mock==2.0.0', 'django_nose==1.4.4', 'tox==2.3.1'], + install_requires=get_requirements('requirements/common.txt') + ["django>=1.8,<2.0.0"], + tests_require=get_requirements('requirements/development.txt'), license="BSD", zip_safe=False, keywords='django shard sharding library', @@ -24,7 +29,8 @@ 'Natural Language :: English', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5' + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6' ], test_suite='runtests.run_tests', ) diff --git a/tests/test_router.py b/tests/test_router.py index 770470f..ff989ac 100644 --- a/tests/test_router.py +++ b/tests/test_router.py @@ -4,7 +4,7 @@ from django.conf import settings from django.contrib.auth import get_user_model from django.contrib.auth.models import Group -from django.test import TransactionTestCase +from django.test import TransactionTestCase, override_settings from tests.models import TestModel, ShardedTestModelIDs, PostgresCustomAutoIDModel, PostgresShardUser from django_sharding_library.exceptions import InvalidMigrationException @@ -524,6 +524,42 @@ def test_lookup_fallback_if_migration_directory_not_the_same_as_the_model(self): can_migrate_shard=False, ) + @override_settings(DJANGO_SHARDING_SETTINGS={"DELETED_MODELS": {"deleted.Whatever": {"database": "app_shard_002"}}}) + def test_deleted_model_in_settings__specific_database(self): + self.assertFalse(self.sut.allow_migrate(model_name="deleted.Whatever", db='default', app_label='deleted', **{})) + self.assertTrue(self.sut.allow_migrate(model_name="deleted.Whatever", db='app_shard_002', app_label='deleted', **{})) + + self.assertFalse(self.sut.allow_migrate(model_name="Whatever", db='default', app_label='deleted', **{})) + self.assertTrue(self.sut.allow_migrate(model_name="Whatever", db='app_shard_002', app_label='deleted', **{})) + + @override_settings(DJANGO_SHARDING_SETTINGS={"DELETED_MODELS": {"deleted.Whatever": {"shard_group": "default"}}}) + def test_deleted_model_in_settings__shard_group(self): + self.assertFalse(self.sut.allow_migrate(model_name="deleted.Whatever", db='default', app_label='deleted', **{})) + self.assertTrue(self.sut.allow_migrate(model_name="deleted.Whatever", db='app_shard_001', app_label='deleted', **{})) + self.assertTrue(self.sut.allow_migrate(model_name="deleted.Whatever", db='app_shard_002', app_label='deleted', **{})) + self.assertFalse(self.sut.allow_migrate(model_name="deleted.Whatever", db='app_shard_001_replica_001', app_label='deleted', **{})) + self.assertFalse(self.sut.allow_migrate(model_name="deleted.Whatever", db='app_shard_001_replica_002', app_label='deleted', **{})) + + self.assertFalse(self.sut.allow_migrate(model_name="Whatever", db='default', app_label='deleted', **{})) + self.assertTrue(self.sut.allow_migrate(model_name="Whatever", db='app_shard_001', app_label='deleted', **{})) + self.assertTrue(self.sut.allow_migrate(model_name="Whatever", db='app_shard_002', app_label='deleted', **{})) + self.assertFalse(self.sut.allow_migrate(model_name="Whatever", db='app_shard_001_replica_001', app_label='deleted', **{})) + self.assertFalse(self.sut.allow_migrate(model_name="Whatever", db='app_shard_001_replica_002', app_label='deleted', **{})) + + @override_settings(DJANGO_SHARDING_SETTINGS={"DELETED_MODELS": {"deleted.Whatever": None}}) + def test_deleted_model_in_settings__unsharded(self): + self.assertTrue(self.sut.allow_migrate(model_name="deleted.Whatever", db='default', app_label='deleted', **{})) + self.assertFalse(self.sut.allow_migrate(model_name="deleted.Whatever", db='app_shard_001', app_label='deleted', **{})) + self.assertFalse(self.sut.allow_migrate(model_name="deleted.Whatever", db='app_shard_002', app_label='deleted', **{})) + self.assertFalse(self.sut.allow_migrate(model_name="deleted.Whatever", db='app_shard_001_replica_001', app_label='deleted', **{})) + self.assertFalse(self.sut.allow_migrate(model_name="deleted.Whatever", db='app_shard_001_replica_002', app_label='deleted', **{})) + + self.assertTrue(self.sut.allow_migrate(model_name="Whatever", db='default', app_label='deleted', **{})) + self.assertFalse(self.sut.allow_migrate(model_name="Whatever", db='app_shard_001', app_label='deleted', **{})) + self.assertFalse(self.sut.allow_migrate(model_name="Whatever", db='app_shard_002', app_label='deleted', **{})) + self.assertFalse(self.sut.allow_migrate(model_name="Whatever", db='app_shard_001_replica_001', app_label='deleted', **{})) + self.assertFalse(self.sut.allow_migrate(model_name="Whatever", db='app_shard_001_replica_002', app_label='deleted', **{})) + class RouterForPostgresIDFieldTest(TransactionTestCase): diff --git a/tox.ini b/tox.ini index 3f1a2a9..09b0d60 100644 --- a/tox.ini +++ b/tox.ini @@ -1,8 +1,7 @@ [tox] envlist = py27-dj{18,19,110,111,dev} - py34-dj{18,19,110,111,dev} - py35-dj{18,19,110,111,dev} + py36-dj{18,19,110,111,dev} [testenv] passenv= @@ -16,12 +15,10 @@ passenv= TRAVIS basepython = py27: python2.7 - py34: python3.4 - py35: python3.5 + py36: python3.6 deps = coverage - -r{toxinidir}/requirements/_without_django.txt - -r{toxinidir}/requirements/development.txt + -r{toxinidir}/requirements.txt dj18: Django>=1.8,<1.9 dj19: Django>=1.9,<1.10 dj110: Django>=1.10,<1.11