# Path: django_sharding_library/constants.py
# class Backends(object):
#     MYSQL = ('django.db.backends.mysql', 'django.contrib.gis.db.backends.mysql')
#     POSTGRES = ('django.db.backends.postgresql_psycopg2', 'django.db.backends.postgresql', 'django.contrib.gis.db.backends.postgis')
#     SQLITE = ('django.db.backends.sqlite3', 'django.contrib.gis.db.backends.spacialite')
# 
# Path: django_sharding_library/exceptions.py
# class NonExistentDatabaseException(DjangoShardingException):
#     pass
# 
# class ShardedModelInitializationException(DjangoShardingException):
#     pass
# 
# Path: django_sharding_library/fields.py
# class ShardedIDFieldMixin(object):
#     """
#     An autoincrimenting field which takes an id generator class instance
#     as an argument and uses the generator to assign each new object a unique
#     id.
#     Note: This currently must be the primary key of the model and although this
#     may be updated in the future, it should not hinder the app to use other
#     candidates as unique fields.
#     """
#     def __init__(self, *args, **kwargs):
#         # Remove the strategy from the kwargs so that it doesn't get passed to Django.
#         setattr(self, 'strategy', kwargs['strategy'])
#         del kwargs['strategy']
#         return super(ShardedIDFieldMixin, self).__init__(*args, **kwargs)
# 
#     def deconstruct(self):
#         name, path, args, kwargs = super(ShardedIDFieldMixin, self).deconstruct()
# 
#         # Add the strategy from the kwargs so that it does get passed to our model.
#         kwargs['strategy'] = getattr(self, 'strategy')
#         return name, path, args, kwargs
# 
#     def get_pk_value_on_save(self, instance):
#         if not instance.pk:
#             return self.strategy.get_next_id()
#         return instance.pk
# 
# class BasePostgresShardGeneratedIDField(object):
# 
#     def __init__(self, *args, **kwargs):
# 
#         if not hasattr(settings, 'SHARD_EPOCH'):
#             raise ValueError("PostgresShardGeneratedIDField requires a SHARD_EPOCH to be defined in your settings file.")
# 
#         return super(BasePostgresShardGeneratedIDField, self).__init__(*args, **kwargs)
# 
#     @staticmethod
#     def migration_receiver(*args, **kwargs):
#         sequence_name = "global_id_sequence"
#         db_alias = kwargs.get('using')
#         if not db_alias:
#             raise EnvironmentError("A pre-migration receiver did not receive a database alias. "
#                                    "Perhaps your app is not registered correctly?")
#         if settings.DATABASES[db_alias]['ENGINE'] in Backends.POSTGRES:
#             shard_id = settings.DATABASES[db_alias].get('SHARD_ID', 0)
#             create_postgres_global_sequence(sequence_name, db_alias, True)
#             create_postgres_shard_id_function(sequence_name, db_alias, shard_id)
# 
# Path: django_sharding_library/utils.py
# def register_migration_signal_for_model_receiver(model, function, dispatch_uid=None):
#     signals.pre_migrate.connect(function, sender=model, dispatch_uid=dispatch_uid)


from six import iteritems
from django.conf import settings
from django.apps import apps
from django_sharding_library.constants import Backends
from django_sharding_library.exceptions import NonExistentDatabaseException, ShardedModelInitializationException
from django_sharding_library.fields import ShardedIDFieldMixin, BasePostgresShardGeneratedIDField
from django_sharding_library.utils import register_migration_signal_for_model_receiver
        return cls
    return configure


def model_config(shard_group=None, database=None, skip_runtime_checks=False):
    """
    A decorator for marking a model as being either sharded or stored on a
    particular database. When sharding, it does some verification to ensure
    that the model is defined correctly.
    """
    def configure(cls):
        if database and shard_group:
            raise ShardedModelInitializationException('A model cannot be both sharded and stored on a particular database.')

        if not database and not shard_group:
            raise ShardedModelInitializationException('The model should be either sharded or stored on a database in the `model_config` decorator is used.')

        if database:
            if (database not in settings.DATABASES or settings.DATABASES[database].get('PRIMARY')) and skip_runtime_checks is False:
                raise NonExistentDatabaseException(
                    'Unable to place {} in {} as that is not an existing primary database in the system.'.format(cls._meta.model_name, database)
                )
            setattr(cls, 'django_sharding__database', database)

        postgres_shard_id_fields = list(filter(lambda field: issubclass(type(field), BasePostgresShardGeneratedIDField), cls._meta.fields))
        if postgres_shard_id_fields and skip_runtime_checks is False:
            database_dicts = [settings.DATABASES[database]] if database else [db_settings for db, db_settings in
                                                                              iteritems(settings.DATABASES) if
                                                                              db_settings["SHARD_GROUP"] == shard_group]
            if any([database_dict['ENGINE'] not in Backends.POSTGRES for database_dict in database_dicts]):

# Path: django_sharding_library/constants.py
# class Backends(object):
#     MYSQL = ('django.db.backends.mysql', 'django.contrib.gis.db.backends.mysql')
#     POSTGRES = ('django.db.backends.postgresql_psycopg2', 'django.db.backends.postgresql', 'django.contrib.gis.db.backends.postgis')
#     SQLITE = ('django.db.backends.sqlite3', 'django.contrib.gis.db.backends.spacialite')
# 
# Path: django_sharding_library/exceptions.py
# class NonExistentDatabaseException(DjangoShardingException):
#     pass
# 
# class ShardedModelInitializationException(DjangoShardingException):
#     pass
# 
# Path: django_sharding_library/fields.py
# class ShardedIDFieldMixin(object):
#     """
#     An autoincrimenting field which takes an id generator class instance
#     as an argument and uses the generator to assign each new object a unique
#     id.
#     Note: This currently must be the primary key of the model and although this
#     may be updated in the future, it should not hinder the app to use other
#     candidates as unique fields.
#     """
#     def __init__(self, *args, **kwargs):
#         # Remove the strategy from the kwargs so that it doesn't get passed to Django.
#         setattr(self, 'strategy', kwargs['strategy'])
#         del kwargs['strategy']
#         return super(ShardedIDFieldMixin, self).__init__(*args, **kwargs)
# 
#     def deconstruct(self):
#         name, path, args, kwargs = super(ShardedIDFieldMixin, self).deconstruct()
# 
#         # Add the strategy from the kwargs so that it does get passed to our model.
#         kwargs['strategy'] = getattr(self, 'strategy')
#         return name, path, args, kwargs
# 
#     def get_pk_value_on_save(self, instance):
#         if not instance.pk:
#             return self.strategy.get_next_id()
#         return instance.pk
# 
# class BasePostgresShardGeneratedIDField(object):
# 
#     def __init__(self, *args, **kwargs):
# 
#         if not hasattr(settings, 'SHARD_EPOCH'):
#             raise ValueError("PostgresShardGeneratedIDField requires a SHARD_EPOCH to be defined in your settings file.")
# 
#         return super(BasePostgresShardGeneratedIDField, self).__init__(*args, **kwargs)
# 
#     @staticmethod
#     def migration_receiver(*args, **kwargs):
#         sequence_name = "global_id_sequence"
#         db_alias = kwargs.get('using')
#         if not db_alias:
#             raise EnvironmentError("A pre-migration receiver did not receive a database alias. "
#                                    "Perhaps your app is not registered correctly?")
#         if settings.DATABASES[db_alias]['ENGINE'] in Backends.POSTGRES:
#             shard_id = settings.DATABASES[db_alias].get('SHARD_ID', 0)
#             create_postgres_global_sequence(sequence_name, db_alias, True)
#             create_postgres_shard_id_function(sequence_name, db_alias, shard_id)
# 
# Path: django_sharding_library/utils.py
# def register_migration_signal_for_model_receiver(model, function, dispatch_uid=None):
#     signals.pre_migrate.connect(function, sender=model, dispatch_uid=dispatch_uid)


# Path: django_sharding_library/decorators.py
from six import iteritems
from django.conf import settings
from django.apps import apps
from django_sharding_library.constants import Backends
from django_sharding_library.exceptions import NonExistentDatabaseException, ShardedModelInitializationException
from django_sharding_library.fields import ShardedIDFieldMixin, BasePostgresShardGeneratedIDField
from django_sharding_library.utils import register_migration_signal_for_model_receiver

        return cls
    return configure


def model_config(shard_group=None, database=None, skip_runtime_checks=False):
    """
    A decorator for marking a model as being either sharded or stored on a
    particular database. When sharding, it does some verification to ensure
    that the model is defined correctly.
    """
    def configure(cls):
        if database and shard_group:
            raise ShardedModelInitializationException('A model cannot be both sharded and stored on a particular database.')

        if not database and not shard_group:
            raise ShardedModelInitializationException('The model should be either sharded or stored on a database in the `model_config` decorator is used.')

        if database:
            if (database not in settings.DATABASES or settings.DATABASES[database].get('PRIMARY')) and skip_runtime_checks is False:
                raise NonExistentDatabaseException(
                    'Unable to place {} in {} as that is not an existing primary database in the system.'.format(cls._meta.model_name, database)
                )
            setattr(cls, 'django_sharding__database', database)

        postgres_shard_id_fields = list(filter(lambda field: issubclass(type(field), BasePostgresShardGeneratedIDField), cls._meta.fields))
        if postgres_shard_id_fields and skip_runtime_checks is False:
            database_dicts = [settings.DATABASES[database]] if database else [db_settings for db, db_settings in
                                                                              iteritems(settings.DATABASES) if
                                                                              db_settings["SHARD_GROUP"] == shard_group]
            if any([database_dict['ENGINE'] not in Backends.POSTGRES for database_dict in database_dicts]):

                raise ShardedModelInitializationException(