text: from copy import copy
  import six
  import sqlalchemy as sa
  from sqlalchemy.ext.declarative import declared_attr
  from sqlalchemy.orm import column_property
  from sqlalchemy_utils.functions import get_declarative_base
  
  from .utils import adapt_columns, option
  from .version import VersionClassBase
  
  
  def find_closest_versioned_parent(manager, model):
      """
      Finds the closest versioned parent for current parent model.
      """
      for class_ in model.__bases__:
          if class_ in manager.version_class_map:
              return manager.version_class_map[class_]
  
  def versioned_parents(manager, model):
      """
      Finds all versioned ancestors for current parent model.
      """
      for class_ in model.__mro__:
          if class_ in manager.version_class_map:
              yield manager.version_class_map[class_]
  
  
  def get_base_class(manager, model):
      """
      Returns all base classes for history model.
      """
      return (
          option(model, 'base_classes')
          or
          (get_declarative_base(model), )
      )
  
  
  def version_base(manager, parent_cls, base_class_factory=None):
      if base_class_factory is None:
          base_class_factory = get_base_class
  
      VersionBase = find_closest_versioned_parent(manager, parent_cls)
  
      if not VersionBase:
          VersionBase = type(
              'VersionBase',
              (base_class_factory(manager, parent_cls) + (VersionClassBase, )),
              {'__abstract__': True}
          )
  
      return VersionBase
  
  
  def copy_mapper_args(model):
      args = {}
      if hasattr(model, '__mapper_args__'):
          arg_names = (
              'with_polymorphic',
              'polymorphic_identity',
              'concrete'
          )
          for arg in arg_names:
              if arg in model.__mapper_args__:
                  args[arg] = (
                      model.__mapper_args__[arg]
                  )
  
          if 'order_by' in model.__mapper_args__:
              arg = model.__mapper_args__['order_by']
              # Only allow string based order_by reflection to version
              # classes.
              if isinstance(arg, six.string_types):
                  args['order_by'] = arg
  
          if 'polymorphic_on' in model.__mapper_args__:
              column = model.__mapper_args__['polymorphic_on']
              if isinstance(column, six.string_types):
                  args['polymorphic_on'] = column
              else:
                  args['polymorphic_on'] = column.key
      return args
  
  
  class ModelBuilder(object):
      """
      VersionedModelBuilder handles the building of Version models based on
      parent table attributes and versioning configuration.
      """
      def __init__(self, versioning_manager, model):
          """
          :param versioning_manager:
              VersioningManager object
          :param model:
              SQLAlchemy declarative model object that acts as a parent for the
              built version model
          """
          self.manager = versioning_manager
          self.model = model
  
      def build_parent_relationship(self):
          """
          Builds a relationship between currently built version class and
          parent class (the model whose history the currently build version
          class represents).
          """
          conditions = []
          foreign_keys = []
          model_keys = []
          for key, column in sa.inspect(self.model).columns.items():
              if column.primary_key:
                  conditions.append(
                      getattr(self.model, key)
                      ==
                      getattr(self.version_class, key)
                  )
                  foreign_keys.append(
                      getattr(self.version_class, key)
                  )
                  model_keys.append(
                      getattr(self.model, key)
                  )
  
          # We need to check if versions relation was already set for parent
          # class.
          if not hasattr(self.model, 'versions'):
              self.model.versions = sa.orm.relationship(
                  self.version_class,
                  primaryjoin=sa.and_(*conditions),
                  foreign_keys=foreign_keys,
                  order_by=lambda: getattr(
                      self.version_class,
                      option(self.model, 'transaction_column_name')
                  ),
                  lazy='dynamic',
                  viewonly=True
              )
              # We must explicitly declare this relationship, instead of
              # specifying as a backref to the one above, since they are
              # viewonly=True and SQLAlchemy will warn if using backref.
              self.version_class.version_parent = sa.orm.relationship(
                  self.model,
                  primaryjoin=sa.and_(*conditions),
                  foreign_keys=model_keys,
                  viewonly=True,
                  uselist=False,
              )
  
      def build_transaction_relationship(self, tx_class):
          """
          Builds a relationship between currently built version class and
          Transaction class.
  
          :param tx_class: Transaction class
          """
          # Only define transaction relation if it doesn't already exist in
          # parent class.
  
          transaction_column = getattr(
              self.version_class,
              option(self.model, 'transaction_column_name')
          )
  
          if not hasattr(self.version_class, 'transaction'):
              self.version_class.transaction = sa.orm.relationship(
                  tx_class,
                  primaryjoin=tx_class.id == transaction_column,
                  foreign_keys=[transaction_column],
              )
  
      def base_classes(self):
          """
          Returns all base classes for history model.
          """
          return (version_base(self.manager, self.model), )
  
      def inheritance_args(self, cls, version_table, table):
          """
          Return mapper inheritance args for currently built history model.
          """
          args = {}
  
          if not sa.inspect(self.model).single:
              parent = find_closest_versioned_parent(
                  self.manager, self.model
              )
              if parent:
                  # The version classes do not contain foreign keys, hence we
                  # need to map inheritance condition manually for classes that
                  # use joined table inheritance
                  if parent.__table__.name != table.name:
                      mapper = sa.inspect(self.model)
  
                      inherit_condition = adapt_columns(
                          self.model,
                          mapper.inherit_condition
                      )
                      tx_column_name = self.manager.options[
                          'transaction_column_name'
                      ]
                      args['inherit_condition'] = sa.and_(
                          inherit_condition,
                          getattr(parent.__table__.c, tx_column_name) ==
                          getattr(cls.__table__.c, tx_column_name)
                      )
                      args['inherit_foreign_keys'] = [
                          version_table.c[column.key]
                          for column in sa.inspect(self.model).columns
                          if column.primary_key
                      ]
  
          args.update(copy_mapper_args(self.model))
  
          return args
  
      def get_inherited_denormalized_columns(self, table):
          parent_models = list(versioned_parents(self.manager, self.model))
          mapper = sa.inspect(self.model)
          args = {}
  
          if parent_models and not (mapper.single or mapper.concrete):
              columns = [
                  self.manager.option(self.model, 'operation_type_column_name'),
                  self.manager.option(self.model, 'transaction_column_name')
              ]
              if self.manager.option(self.model, 'strategy') == 'validity':
                  columns.append(
                      self.manager.option(
                          self.model,
                          'end_transaction_column_name'
                      )
                  )
  
              for column in columns:
                  args[column] = column_property(
                      table.c[column],
                      *[m.__table__.c[column] for m in parent_models]
                  )
          return args
  
      def build_model(self, table):
          """
          Build history model class.
          """
          args = {}
  
          @declared_attr
          def mapper_args(cls):
              mapper_args = {}
              mapper_args.update(self.inheritance_args(
                  cls, table, self.model.__table__)
              )
              return mapper_args
  
          args['__mapper_args__'] = mapper_args
          args['__versioning_manager__'] = self.manager
          args['__version_parent__'] = self.model
  
          parent = find_closest_versioned_parent(self.manager, self.model)
  
          if not parent or parent.__table__.name != table.name:
              args['__table__'] = table
  
          args.update(self.get_inherited_denormalized_columns(table))
  
          if self.manager.options.get('use_module_name', True):
              name = '%s%sVersion' % (
                  self.model.__module__.title().replace('.', ''),
                  self.model.__name__
              )
          else:
              name = '%sVersion' % (self.model.__name__,)
          return type(name, self.base_classes(), args)
  
      def __call__(self, table, tx_class):
          """
          Build history model and relationships to parent model, transaction
          log model.
          """
          # versioned attributes need to be copied for each child class,
          # otherwise each child class would share the same __versioned__
          # option dict
          self.model.__versioned__ = copy(self.model.__versioned__)
          self.model.__versioning_manager__ = self.manager
          self.version_class = self.build_model(table)
          self.build_parent_relationship()
          self.build_transaction_relationship(tx_class)
          return self.version_class
  