Exemplo n.º 1
0
    class MockConnection(base.Connectable):
        def __init__(self, dialect, execute):
            self._dialect = dialect
            self.execute = execute

        engine = property(lambda s: s)
        dialect = property(attrgetter('_dialect'))
        name = property(lambda s: s._dialect.name)

        schema_for_object = schema._schema_getter(None)

        def contextual_connect(self, **kwargs):
            return self

        def execution_options(self, **kw):
            return self

        def compiler(self, statement, parameters, **kwargs):
            return self._dialect.compiler(statement,
                                          parameters,
                                          engine=self,
                                          **kwargs)

        def create(self, entity, **kwargs):
            kwargs['checkfirst'] = False
            from sqlalchemy.engine import ddl

            ddl.SchemaGenerator(self.dialect, self,
                                **kwargs).traverse_single(entity)

        def drop(self, entity, **kwargs):
            kwargs['checkfirst'] = False
            from sqlalchemy.engine import ddl
            ddl.SchemaDropper(self.dialect, self,
                              **kwargs).traverse_single(entity)

        def _run_visitor(self,
                         visitorcallable,
                         element,
                         connection=None,
                         **kwargs):
            kwargs['checkfirst'] = False
            visitorcallable(self.dialect, self,
                            **kwargs).traverse_single(element)

        def execute(self, object, *multiparams, **params):
            raise NotImplementedError()
Exemplo n.º 2
0
class GinoConnection:
    """
    Represents an actual database connection.

    This is the root of all query API like :meth:`all`, :meth:`first`,
    :meth:`one`, :meth:`one_or_none`, :meth:`scalar` or :meth:`status`,
    those on engine or query are simply wrappers of methods in this class.

    Usually instances of this class are created by :meth:`.GinoEngine.acquire`.

    .. note::

        :class:`.GinoConnection` may refer to zero or one underlying database
        connection - when a :class:`.GinoConnection` is acquired with
        ``lazy=True``, the underlying connection may still be in the pool,
        until a query API is called or :meth:`get_raw_connection` is called.

        Oppositely, one underlying database connection can be shared by many
        :class:`.GinoConnection` instances when they are acquired with
        ``reuse=True``. The actual database connection is only returned to the
        pool when the **root** :class:`.GinoConnection` is released. Read more
        in :meth:`GinoEngine.acquire` method.

    .. seealso::

        :doc:`/explanation/engine`

    """

    # noinspection PyProtectedMember
    schema_for_object = schema._schema_getter(None)
    """A SQLAlchemy compatibility attribute, don't use it for now, it bites."""
    def __init__(self, dialect, sa_conn, stack=None):
        self._dialect = dialect
        self._sa_conn = sa_conn
        self._stack = stack

    @property
    def _dbapi_conn(self):
        return self._sa_conn.connection

    @property
    def raw_connection(self):
        """
        The current underlying database connection instance, type depends on
        the dialect in use. May be ``None`` if self is a lazy connection.

        """
        return self._dbapi_conn.raw_connection

    async def get_raw_connection(self, *, timeout=None):
        """
        Get the underlying database connection, acquire one if none present.

        :param timeout: Seconds to wait for the underlying acquiring
        :return: Underlying database connection instance depending on the
                 dialect in use
        :raises: :class:`~asyncio.TimeoutError` if the acquiring timed out

        """
        return await self._dbapi_conn.acquire(timeout=timeout)

    async def release(self, *, permanent=True):
        """
        Returns the underlying database connection to its pool.

        If ``permanent=False``, this connection will be set in lazy mode with
        underlying database connection returned, the next query on this
        connection will cause a new database connection acquired. This is
        useful when this connection may still be useful again later, while some
        long-running I/O operations are about to take place, which should not
        take up one database connection or even transaction for that long time.

        Otherwise with ``permanent=True`` (default), this connection will be
        marked as closed after returning to pool, and be no longer usable
        again.

        If this connection is a reusing connection, then only this connection
        is closed (depending on ``permanent``), the reused underlying
        connection will **not** be returned back to the pool.

        Practically it is recommended to return connections in the reversed
        order as they are borrowed, but if this connection is a reused
        connection with still other opening connections reusing it, then on
        release the underlying connection **will be** returned to the pool,
        with all the reusing connections losing an available underlying
        connection. The availability of further operations on those reusing
        connections depends on the given ``permanent`` value.

        .. seealso::

            :meth:`.GinoEngine.acquire`

        """
        if permanent and self._stack is not None:
            dbapi_conn = self._stack.remove(lambda x: x.gino_conn is self)
            if dbapi_conn:
                await dbapi_conn.release(True)
            else:
                raise ValueError("This connection is already released.")
        else:
            await self._dbapi_conn.release(permanent)

    @property
    def dialect(self):
        """
        The :class:`~sqlalchemy.engine.interfaces.Dialect` in use, inherited
        from the engine created this connection.

        """
        return self._dialect

    def _execute(self, clause, multiparams, params):
        return self._sa_conn.execute(clause, *multiparams, **params)

    async def all(self, clause, *multiparams, **params):
        """
        Runs the given query in database, returns all results as a list.

        This method accepts the same parameters taken by SQLAlchemy
        :meth:`~sqlalchemy.engine.Connectable.execute`. You can pass in a raw
        SQL string, or *any* SQLAlchemy query clauses.

        If the given query clause is built by CRUD models, then the returning
        rows will be turned into relevant model objects (Only one type of model
        per query is supported for now, no relationship support yet). See
        :meth:`execution_options` for more information.

        If the given parameters are parsed as "executemany" - bulk inserting
        multiple rows in one call for example, the returning result from
        database will be discarded and this method will return ``None``.

        """
        result = self._execute(clause, multiparams, params)
        return await result.execute()

    async def first(self, clause, *multiparams, **params):
        """
        Runs the given query in database, returns the first result.

        If the query returns no result, this method will return ``None``.

        See :meth:`all` for common query comments.

        """
        result = self._execute(clause, multiparams, params)
        return await result.execute(one=True)

    async def _first_with_context(self, clause, *multiparams, **params):
        result = self._execute(clause, multiparams, params)
        return await result.execute(one=True, return_context=True)

    async def one_or_none(self, clause, *multiparams, **params):
        """
        Runs the given query in database, returns at most one result.

        If the query returns no result, this method will return ``None``.
        If the query returns multiple results, this method will raise
        :class:`~gino.exceptions.MultipleResultsFound`.

        See :meth:`all` for common query comments.

        """
        result = self._execute(clause, multiparams, params)
        ret = await result.execute()

        if ret is None or len(ret) == 0:
            return None

        if len(ret) == 1:
            return ret[0]

        raise MultipleResultsFound("Multiple rows found for one_or_none()")

    async def one(self, clause, *multiparams, **params):
        """
        Runs the given query in database, returns exactly one result.

        If the query returns no result, this method will raise
        :class:`~gino.exceptions.NoResultFound`.
        If the query returns multiple results, this method will raise
        :class:`~gino.exceptions.MultipleResultsFound`.

        See :meth:`all` for common query comments.

        """
        try:
            ret = await self.one_or_none(clause, *multiparams, **params)
        except MultipleResultsFound:
            raise MultipleResultsFound("Multiple rows found for one()")

        if ret is None:
            raise NoResultFound("No row was found for one()")

        return ret

    async def scalar(self, clause, *multiparams, **params):
        """
        Runs the given query in database, returns the first result.

        If the query returns no result, this method will return ``None``.

        See :meth:`all` for common query comments.

        """
        result = self._execute(clause, multiparams, params)
        rv = await result.execute(one=True, return_model=False)
        if rv:
            return rv[0]
        else:
            return None

    async def status(self, clause, *multiparams, **params):
        """
        Runs the given query in database, returns the query status.

        The returning query status depends on underlying database and the
        dialect in use. For asyncpg it is a string, you can parse it like this:
        https://git.io/v7oze

        """
        result = self._execute(clause, multiparams, params)
        return await result.execute(status=True)

    def transaction(self, *args, **kwargs):
        """
        Starts a database transaction.

        There are two ways using this method: **managed** as an asynchronous
        context manager::

            async with conn.transaction() as tx:
                # run query in transaction

        or **manually** awaited::

            tx = await conn.transaction()
            try:
                # run query in transaction
                await tx.commit()
            except Exception:
                await tx.rollback()
                raise

        Where the ``tx`` is an instance of the
        :class:`~gino.transaction.GinoTransaction` class, feel free to read
        more about it.

        In the first managed mode, the transaction is automatically committed
        on exiting the context block, or rolled back if an exception was raised
        which led to the exit of the context. In the second manual mode, you'll
        need to manually call the
        :meth:`~gino.transaction.GinoTransaction.commit` or
        :meth:`~gino.transaction.GinoTransaction.rollback` methods on need.

        If this is a lazy connection, entering a transaction will cause a new
        database connection acquired if none was present.

        Transactions may support nesting depending on the dialect in use. For
        example in asyncpg, starting a second transaction on the same
        connection will create a save point in the database.

        For now, the parameters are directly passed to underlying database
        driver, read :meth:`asyncpg.connection.Connection.transaction` for
        asyncpg.

        """
        return GinoTransaction(self, args, kwargs)

    def iterate(self, clause, *multiparams, **params):
        """
        Creates a server-side cursor in database for large query results.

        Cursors must work within transactions::

            async with conn.transaction():
                async for user in conn.iterate(User.query):
                    # handle each user without loading all users into memory

        Alternatively, you can manually control how the cursor works::

            async with conn.transaction():
                cursor = await conn.iterate(User.query)
                user = await cursor.next()
                users = await cursor.many(10)

        Read more about how :class:`~gino.dialects.base.Cursor` works.

        Similarly, this method takes the same parameters as :meth:`all`.

        """
        result = self._execute(clause, multiparams, params)
        return result.iterate()

    def execution_options(self, **opt):
        """
        Set non-SQL options for the connection which take effect during
        execution.

        This method returns a copy of this :class:`.GinoConnection` which
        references the same underlying database connection, but with the given
        execution options set on the copy. Therefore, it is a good practice to
        discard the copy immediately after use, for example::

            row = await conn.execution_options(model=None).first(User.query)

        This is very much the same as SQLAlchemy
        :meth:`~sqlalchemy.engine.base.Connection.execution_options`, it
        actually does pass the execution options to the underlying SQLAlchemy
        :class:`~sqlalchemy.engine.base.Connection`. Furthermore, GINO added a
        few execution options:

        :param return_model: Boolean to control whether the returning results
          should be loaded into model instances, where the model class is
          defined in another execution option ``model``. Default is ``True``.

        :param model: Specifies the type of model instance to create on return.
          This has no effect if ``return_model`` is set to ``False``. Usually
          in queries built by CRUD models, this execution option is
          automatically set. For now, GINO only supports loading each row into
          one type of model object, relationships are not supported. Please use
          multiple queries for that. ``None`` for no postprocessing (default).

        :param timeout: Seconds to wait for the query to finish. ``None`` for
          no time out (default).

        :param loader: A loader expression to load the database rows into
          specified objective structure. It can be either:

          * A model class, so that the query will yield model instances of this
            class. It is your responsibility to make sure all the columns of
            this model is selected in the query.
          * A :class:`~sqlalchemy.schema.Column` instance, so that each result
            will be only a single value of this column. Please note, if you
            want to achieve fetching the very first value, you should use
            :meth:`~gino.engine.GinoConnection.first` instead of
            :meth:`~gino.engine.GinoConnection.scalar`. However, using directly
            :meth:`~gino.engine.GinoConnection.scalar` is a more direct way.
          * A tuple nesting more loader expressions recursively.
          * A :func:`callable` function that will be called for each row to
            fully customize the result. Two positional arguments will be passed
            to the function: the first is the :class:`row
            <sqlalchemy.engine.RowProxy>` instance, the second is a context
            object which is only present if nested else ``None``.
          * A :class:`~gino.loader.Loader` instance directly.
          * Anything else will be treated as literal values thus returned as
            whatever they are.

        """
        return type(self)(self._dialect,
                          self._sa_conn.execution_options(**opt))

    async def _run_visitor(self, visitorcallable, element, **kwargs):
        await visitorcallable(self.dialect, self,
                              **kwargs).traverse_single(element)

    async def prepare(self, clause):
        return await self._execute(clause, (_bypass_no_param, ),
                                   {}).prepare(clause)