Exemplo n.º 1
0
class QueryMaker(object):
    def __init__(
            self,
            # An optional `Declarative class <http://docs.sqlalchemy.org/en/latest/orm/tutorial.html#declare-a-mapping>`_ to query.
            declarative_class=None,
            # Optionally, begin with an existing query_.
            query=None):

        if declarative_class:
            assert _is_mapped_class(declarative_class)

        # If a query is provided, try to infer the declarative_class.
        if query is not None:
            assert isinstance(query, Query)
            self._query = query
            try:
                self._select = self._get_joinpoint_zero_class()
            except:
                # We can't infer it. Use what's provided instead, and add this to the query.
                assert declarative_class
                self._select = declarative_class
                self._query = self._query.select_from(declarative_class)
            else:
                # If a declarative_class was provided, make sure it's consistent with the inferred class.
                if declarative_class:
                    assert declarative_class is self._select
        else:
            # The declarative class must be provided if the query wasn't.
            assert declarative_class
            # Since a query was not provied, create an empty `query <http://docs.sqlalchemy.org/en/latest/orm/query.html>`_; ``to_query`` will fill in the missing information.
            self._query = Query([]).select_from(declarative_class)
            # Keep track of the last selectable construct, to generate the select in ``to_query``.
            self._select = declarative_class

    # Copied verbatim from ``sqlalchemy.orm.query.Query._clone``. This adds the support needed for the _`generative` interface. (Mostly) quoting from query_, "QueryMaker_ features a generative interface whereby successive calls return a new QueryMaker_ object, a copy of the former with additional criteria and options associated with it."
    def _clone(self):
        cls = self.__class__
        q = cls.__new__(cls)
        q.__dict__ = self.__dict__.copy()
        return q

    # Looking up a class's `Column <http://docs.sqlalchemy.org/en/latest/core/metadata.html#sqlalchemy.schema.Column>`_ or `relationship <http://docs.sqlalchemy.org/en/latest/orm/relationship_api.html#sqlalchemy.orm.relationship>`_ generates the matching query.
    @_generative()
    def __getattr__(self, name):
        # Find the Column_ or relationship_ in the join point class we're querying.
        attr = getattr(self._get_joinpoint_zero_class(), name)
        # If the attribute refers to a column, save this as a possible select statement. Note that a Column_ gets replaced with an `InstrumentedAttribute <http://docs.sqlalchemy.org/en/latest/orm/internals.html?highlight=instrumentedattribute#sqlalchemy.orm.attributes.InstrumentedAttribute>`_; see `QueryableAttribute <http://docs.sqlalchemy.org/en/latest/orm/internals.html?highlight=instrumentedattribute#sqlalchemy.orm.attributes.QueryableAttribute.property>`_.
        if isinstance(attr.property, ColumnProperty):
            self._select = attr
        elif isinstance(attr.property, RelationshipProperty):
            # Figure out what class this relationship refers to. See `mapper.params.class_ <http://docs.sqlalchemy.org/en/latest/orm/mapping_api.html?highlight=mapper#sqlalchemy.orm.mapper.params.class_>`_.
            declarative_class = attr.property.mapper.class_
            # Update the query by performing the implied join.
            self._query = self._query.join(declarative_class)
            # Save this relationship as a possible select statement.
            self._select = declarative_class
        else:
            # This isn't a Column_ or a relationship_.
            assert False

    # Indexing the object performs the implied filter. For example, ``session(User)['jack']`` implies ``session.query(User).filter(User.name == 'jack')``.
    @_generative()
    def __getitem__(
        self,
        # Most often, this is a key which will be filtered by the ``default_query`` method of the currently-active `Declarative class`_. In the example above, the ``User`` class must define a ``default_query`` to operate on strings. However, it may also be a filter criterion, such as ``session(User)[User.name == 'jack']``.
        key):

        # See if this is a filter criterion; if not, rely in the ``default_query`` defined by the `Declarative class`_ or fall back to the first primary key.
        criteria = None
        jp0_class = self._get_joinpoint_zero_class()
        if isinstance(key, ClauseElement):
            criteria = key
        elif hasattr(jp0_class, 'default_query'):
            criteria = jp0_class.default_query(key)
        if criteria is None:
            pks = inspect(jp0_class).primary_key
            criteria = pks[0] == key
        self._query = self._query.filter(criteria)

    # Support common syntax: ``for x in query_maker:`` converts this to a query and returns results. The session must already have been set.
    def __iter__(self):
        return self.to_query().__iter__()

    # This property returns a `_QueryWrapper`_, a query-like object which transforms returned Query_ values back into this class while leaving other return values unchanged.
    @property
    def q(self):
        return _QueryWrapper(self)

    # Transform this object into a Query_.
    def to_query(
        self,
        # Optionally, the `Session <http://docs.sqlalchemy.org/en/latest/orm/session_api.html?highlight=session#sqlalchemy.orm.session.Session>`_ to run this query in.
        session=None):

        # If a session was specified, use it to produce the query_; otherwise, use the existing query_.
        query = self._query.with_session(session) if session else self._query
        # Choose the correct method to select either a column or a class (e.g. an entity). As noted earlier, a Column_ becomes and InstrumentedAttribute_.
        if isinstance(self._select, InstrumentedAttribute):
            return query.add_columns(self._select)
        else:
            return query.add_entity(self._select)

    # Get the right-most join point in the current query.
    def _get_joinpoint_zero_class(self):
        jp0 = self._query._joinpoint_zero()
        # If the join point was returned as a `Mapper <http://docs.sqlalchemy.org/en/latest/orm/mapping_api.html#sqlalchemy.orm.mapper.Mapper>`_, get the underlying class.
        if isinstance(jp0, Mapper):
            jp0 = jp0.class_
        return jp0