# Path: flask_restless/helpers.py
# def get_model(instance):
#     """Returns the model class of which the specified object is an instance."""
#     return type(instance)
# 
# Path: flask_restless/helpers.py
# def get_related_model(model, relationname):
#     """Gets the class of the model to which `model` is related by the attribute
#     whose name is `relationname`.
# 
#     For example, if we have the model classes ::
# 
#         class Person(Base):
#             __tablename__ = 'person'
#             id = Column(Integer, primary_key=True)
#             articles = relationship('Article')
# 
#         class Article(Base):
#             __tablename__ = 'article'
#             id = Column(Integer, primary_key=True)
#             author_id = Column(Integer, ForeignKey('person.id'))
#             author = relationship('Person')
# 
#     then ::
# 
#         >>> get_related_model(Person, 'articles')
#         <class 'Article'>
#         >>> get_related_model(Article, 'author')
#         <class 'Person'>
# 
#     This function also "sees through" association proxies and returns
#     the model of the proxied remote relation.
# 
#     """
#     mapper = sqlalchemy_inspect(model)
#     attribute = mapper.all_orm_descriptors[relationname]
#     # HACK This is required for Python 3.3 only. I'm guessing it lazily
#     # loads the attribute or something like that.
#     hasattr(model, relationname)
#     return get_related_model_from_attribute(attribute)
# 
# Path: flask_restless/helpers.py
# def primary_key_names(model):
#     """Returns a list of all the primary keys for a model.
# 
#     The returned list contains the name of each primary key as a string.
# 
#     """
#     mapper = sqlalchemy_inspect(model)
#     return [column.name for column in mapper.primary_key]
# 
# Path: flask_restless/helpers.py
# def primary_key_value(instance, as_string=False):
#     """Returns the value of the primary key field of the specified `instance`
#     of a SQLAlchemy model.
# 
#     This essentially a convenience function for::
# 
#         getattr(instance, primary_key_for(instance))
# 
#     If `as_string` is ``True``, try to coerce the return value to a string.
# 
#     """
#     result = getattr(instance, primary_key_for(instance))
#     if not as_string:
#         return result
#     try:
#         return str(result)
#     except UnicodeEncodeError:
#         return url_quote_plus(result.encode('utf-8'))
# 
# Path: flask_restless/helpers.py
# def session_query(session, model):
#     """Returns a SQLAlchemy query object for the specified `model`.
# 
#     If `model` has a ``query`` attribute already, ``model.query`` will be
#     returned. If the ``query`` attribute is callable ``model.query()`` will be
#     returned instead.
# 
#     If `model` has no such attribute, a query based on `session` will be
#     created and returned.
# 
#     """
#     if hasattr(model, 'query'):
#         if callable(model.query):
#             query = model.query()
#         else:
#             query = model.query
#         if hasattr(query, 'filter'):
#             return query
#     return session.query(model)
# 
# Path: flask_restless/search/filters.py
# def create_filters(model, filters):
#     """Returns an iterator over SQLAlchemy filter expressions.
# 
#     The objects generated by this function can be provided as the
#     positional arguments in an invocation of
#     :meth:`sqlalchemy.orm.Query.filter`.
# 
#     `model` is the SQLAlchemy model on which the filters will be
#     applied.
# 
#     `filters` is an iterable of dictionaries representing filter
#     objects, as described in the Flask-Restless documentation
#     (:ref:`filtering`).
# 
#     This function may raise :exc:`FilterParsingError` if there is a
#     problem converting a dictionary filter into an intermediate
#     representation (the :class:`.Filter` object) and
#     :exc:`FilterCreationError` if there is a problem converting the
#     intermediate representation into a SQLAlchemy expression.
# 
#     """
#     from_dict = partial(from_dictionary, model)
#     # `Filter.from_dictionary()` converts the dictionary representation
#     # of a filter object into an intermediate representation, an
#     # instance of :class:`.Filter` that facilitates the construction of
#     # the actual SQLAlchemy code in `create_filter` below.
#     filters = map(from_dict, filters)
#     # Each of these function calls may raise a FilterCreationError.
#     #
#     # TODO In Python 3.3+, this should be `yield from ...`.
#     return map(methodcaller('to_expression'), filters)


from sqlalchemy.orm import aliased
from sqlalchemy.sql import false as FALSE
from ..helpers import get_model
from ..helpers import get_related_model
from ..helpers import primary_key_names
from ..helpers import primary_key_value
from ..helpers import session_query
from .filters import create_filters

def search_relationship(session, instance, relation, filters=None, sort=None,
                        group_by=None, ignorecase=False):
    """Returns a filtered, sorted, and grouped SQLAlchemy query
    restricted to those objects related to a given instance.

    `session` is the SQLAlchemy session in which to create the query.

    `instance` is an instance of a SQLAlchemy model whose relationship
    will be queried.

`   `relation` is a string naming a to-many relationship of `instance`.

    `filters`, `sort`, `group_by`, and `ignorecase` are identical to the
    corresponding arguments of :func:`.search`.

    """
    model = get_model(instance)
    related_model = get_related_model(model, relation)
    query = session_query(session, related_model)

    # Filter by only those related values that are related to `instance`.
    relationship = getattr(instance, relation)
    # TODO In Python 2.7+, this should be a set comprehension.
    primary_keys = set(primary_key_value(inst) for inst in relationship)
    # If the relationship is empty, we can avoid a potentially expensive
    # filtering operation by simply returning an intentionally empty
    # query.
    if not primary_keys:
        return query.filter(FALSE())

# Path: flask_restless/helpers.py
# def get_model(instance):
#     """Returns the model class of which the specified object is an instance."""
#     return type(instance)
# 
# Path: flask_restless/helpers.py
# def get_related_model(model, relationname):
#     """Gets the class of the model to which `model` is related by the attribute
#     whose name is `relationname`.
# 
#     For example, if we have the model classes ::
# 
#         class Person(Base):
#             __tablename__ = 'person'
#             id = Column(Integer, primary_key=True)
#             articles = relationship('Article')
# 
#         class Article(Base):
#             __tablename__ = 'article'
#             id = Column(Integer, primary_key=True)
#             author_id = Column(Integer, ForeignKey('person.id'))
#             author = relationship('Person')
# 
#     then ::
# 
#         >>> get_related_model(Person, 'articles')
#         <class 'Article'>
#         >>> get_related_model(Article, 'author')
#         <class 'Person'>
# 
#     This function also "sees through" association proxies and returns
#     the model of the proxied remote relation.
# 
#     """
#     mapper = sqlalchemy_inspect(model)
#     attribute = mapper.all_orm_descriptors[relationname]
#     # HACK This is required for Python 3.3 only. I'm guessing it lazily
#     # loads the attribute or something like that.
#     hasattr(model, relationname)
#     return get_related_model_from_attribute(attribute)
# 
# Path: flask_restless/helpers.py
# def primary_key_names(model):
#     """Returns a list of all the primary keys for a model.
# 
#     The returned list contains the name of each primary key as a string.
# 
#     """
#     mapper = sqlalchemy_inspect(model)
#     return [column.name for column in mapper.primary_key]
# 
# Path: flask_restless/helpers.py
# def primary_key_value(instance, as_string=False):
#     """Returns the value of the primary key field of the specified `instance`
#     of a SQLAlchemy model.
# 
#     This essentially a convenience function for::
# 
#         getattr(instance, primary_key_for(instance))
# 
#     If `as_string` is ``True``, try to coerce the return value to a string.
# 
#     """
#     result = getattr(instance, primary_key_for(instance))
#     if not as_string:
#         return result
#     try:
#         return str(result)
#     except UnicodeEncodeError:
#         return url_quote_plus(result.encode('utf-8'))
# 
# Path: flask_restless/helpers.py
# def session_query(session, model):
#     """Returns a SQLAlchemy query object for the specified `model`.
# 
#     If `model` has a ``query`` attribute already, ``model.query`` will be
#     returned. If the ``query`` attribute is callable ``model.query()`` will be
#     returned instead.
# 
#     If `model` has no such attribute, a query based on `session` will be
#     created and returned.
# 
#     """
#     if hasattr(model, 'query'):
#         if callable(model.query):
#             query = model.query()
#         else:
#             query = model.query
#         if hasattr(query, 'filter'):
#             return query
#     return session.query(model)
# 
# Path: flask_restless/search/filters.py
# def create_filters(model, filters):
#     """Returns an iterator over SQLAlchemy filter expressions.
# 
#     The objects generated by this function can be provided as the
#     positional arguments in an invocation of
#     :meth:`sqlalchemy.orm.Query.filter`.
# 
#     `model` is the SQLAlchemy model on which the filters will be
#     applied.
# 
#     `filters` is an iterable of dictionaries representing filter
#     objects, as described in the Flask-Restless documentation
#     (:ref:`filtering`).
# 
#     This function may raise :exc:`FilterParsingError` if there is a
#     problem converting a dictionary filter into an intermediate
#     representation (the :class:`.Filter` object) and
#     :exc:`FilterCreationError` if there is a problem converting the
#     intermediate representation into a SQLAlchemy expression.
# 
#     """
#     from_dict = partial(from_dictionary, model)
#     # `Filter.from_dictionary()` converts the dictionary representation
#     # of a filter object into an intermediate representation, an
#     # instance of :class:`.Filter` that facilitates the construction of
#     # the actual SQLAlchemy code in `create_filter` below.
#     filters = map(from_dict, filters)
#     # Each of these function calls may raise a FilterCreationError.
#     #
#     # TODO In Python 3.3+, this should be `yield from ...`.
#     return map(methodcaller('to_expression'), filters)


# Path: flask_restless/search/drivers.py
from sqlalchemy.orm import aliased
from sqlalchemy.sql import false as FALSE
from ..helpers import get_model
from ..helpers import get_related_model
from ..helpers import primary_key_names
from ..helpers import primary_key_value
from ..helpers import session_query
from .filters import create_filters


def search_relationship(session, instance, relation, filters=None, sort=None,
                        group_by=None, ignorecase=False):
    """Returns a filtered, sorted, and grouped SQLAlchemy query
    restricted to those objects related to a given instance.

    `session` is the SQLAlchemy session in which to create the query.

    `instance` is an instance of a SQLAlchemy model whose relationship
    will be queried.

`   `relation` is a string naming a to-many relationship of `instance`.

    `filters`, `sort`, `group_by`, and `ignorecase` are identical to the
    corresponding arguments of :func:`.search`.

    """
    model = get_model(instance)
    related_model = get_related_model(model, relation)
    query = session_query(session, related_model)

    # Filter by only those related values that are related to `instance`.
    relationship = getattr(instance, relation)
    # TODO In Python 2.7+, this should be a set comprehension.
    primary_keys = set(primary_key_value(inst) for inst in relationship)
    # If the relationship is empty, we can avoid a potentially expensive
    # filtering operation by simply returning an intentionally empty
    # query.
    if not primary_keys:
        return query.filter(FALSE())

    query = query.filter(primary_key_value(related_model).in_(primary_keys))