--- /dev/null
+.hint {
+ background: #ffc;
+ border: 1px solid #dda;
+}
transaction will be committed (or rolled back, in case of exception) and
the connection will be closed.
- .. rubric:: Methods you will need every day
-
.. automethod:: connect
Connection parameters can be passed either as a `conninfo string`__ (a
.. __: https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-PARAMKEYWORDS
.. __: https://www.postgresql.org/docs/current/libpq-envars.html
+ .. automethod:: close
.. automethod:: cursor
- .. automethod:: commit
- .. automethod:: rollback
- .. autoattribute:: autocommit
- The property is writable for sync connections, read-only for async
- ones: you can call `~AsyncConnection.set_autocommit()` on those.
+ .. rubric:: Transaction management methods
For details see :ref:`transactions`.
- .. automethod:: close
+ .. automethod:: commit()
+ .. automethod:: rollback()
+ .. automethod:: transaction(savepoint_name: Optional[str] = None, force_rollback: bool = False) -> Transaction
+
+ It must be called as ``with conn.transaction() as tx: ...``
+
+ Inside a transaction block it will not be possible to call `commit()`
+ or `rollback()`.
+
+ .. autoattribute:: autocommit
+ :annotation: bool
+
+ The property is writable for sync connections, read-only for async
+ ones: you should call `!await` `~AsyncConnection.set_autocommit`\
+ :samp:`({value})` instead.
.. rubric:: Checking and configuring the connection state
- .. autoproperty:: closed
- .. autoproperty:: client_encoding
+ .. autoattribute:: closed
+ :annotation: bool
+
+ .. autoattribute:: client_encoding
+ :annotation: str
The property is writable for sync connections, read-only for async
- ones: you can call `~AsyncConnection.set_client_encoding()` on those.
+ ones: you should call `!await` `~AsyncConnection.set_client_encoding`\
+ :samp:`({value})` instead.
.. attribute:: info
.. automethod:: cursor
.. automethod:: commit
.. automethod:: rollback
+
+ .. automethod:: transaction(savepoint_name: Optional[str] = None, force_rollback: bool = False) -> AsyncTransaction
+
+ It must be called as ``async with conn.transaction() as tx: ...``.
+
.. automethod:: notifies
.. automethod:: set_client_encoding
.. automethod:: set_autocommit
.. autoclass:: Notify
:members: channel, payload, pid
+
+.. autoclass:: Transaction(connection: Connection, savepoint_name: Optional[str] = None, force_rollback: bool = False)
+
+ .. autoproperty:: savepoint_name
+ .. autoattribute:: connection
+ :annotation: Connection
+
+.. autoclass:: AsyncTransaction(connection: AsyncConnection, savepoint_name: Optional[str] = None, force_rollback: bool = False)
Binary parameters and results
-----------------------------
-TODO: lift from psycopg2 docs
+TODO
+.. index:: Transactions management
+.. index:: InFailedSqlTransaction
+.. index:: idle in transaction
.. _transactions:
Transaction management
-----------------------
+======================
-TODO
+`!psycopg3` has a behaviour that may result surprising compared to
+:program:`psql`: by default, any database operation will start a new
+transaction. As a consequence, changes made by any cursor of the connection
+will not be visible until `Connection.commit()` is called, and will be
+discarded by `Connection.rollback()`. The following operation on the same
+connection will start a new transaction.
+
+If a database operation fails, the server will refuse further commands, until
+a `~rollback()` is called.
+
+.. hint::
+
+ If a database operation fails with an error message such as
+ *InFailedSqlTransaction: current transaction is aborted, commands ignored
+ until end of transaction block*, it means that **a previous operation
+ failed** and the database session is in a state on error. You need to call
+ `!rollback()` if you want to keep on using the same connection.
+
+The manual commit requirement can be suspended using `~Connection.autocommit`,
+either as connection attribute or as `~psycopg3.Connection.connect()`
+parameter. This may be required to run operations that need to run outside a
+transaction, such as :sql:`CREATE DATABASE`, :sql:`VACUUM`, :sql:`CALL` on
+`stored procedures`__ using transaction control.
+
+.. __: https://www.postgresql.org/docs/current/xproc.html
+
+.. warning::
+
+ By default even a simple :sql:`SELECT` will start a transaction: in
+ long-running programs, if no further action is taken, the session will
+ remain *idle in transaction*, an undesirable condition for several
+ reasons (locks are held by the session, tables bloat...). For long lived
+ scripts, either make sure to terminate a transaction as soon as possible or
+ use an `~Connection.autocommit` connection.
+
+
+.. _transaction-block:
+
+Transaction blocks
+------------------
+
+A more transparent way to make sure that transactions are finalised at the
+right time is to use `!with` `Connection.transaction()` to create a
+transaction block. When the block is entered a transaction is started; when
+leaving the block the transaction is committed, or it is rolled back if an
+exception is raised inside the block.
+
+For instance, an hypothetical but extremely secure bank may have the following
+code to avoid that no accident between the following two lines leaves the
+accounts unbalanced:
+
+.. code:: python
+
+ with conn.transaction():
+ move_money(conn, account1, -100)
+ move_money(conn, account2, +100)
+
+ # The transaction is now committed
+
+Transaction blocks can also be nested (internal transaction blocks are
+implemented using :sql:`SAVEPOINT`): an exception raised inside an inner block
+has a chance of being handled and not fail completely outer operations. The
+following is an example where a series of operation interact with the
+database. Operations are allowed to fail, and we want to store the number of
+operations successfully processed too.
+
+.. code:: python
+
+ with conn.transaction() as tx1:
+ num_ok = 0
+ for operation in operations:
+ try:
+ with conn.transaction() as tx2:
+ unreliable_operation(conn, operation)
+ except Exception:
+ logger.exception(f"{operation} failed")
+ else:
+ num_ok += 1
+
+ save_number_of_successes(conn, num_ok)
+
+If `!unreliable_operation()` causes an error, including an operation causing a
+database error, all its changes will be reverted. The exception bubbles up
+outside the block: in the example it is intercepted by the `!try` so that the
+loop can complete. The outermost loop is unaffected (unless other errors
+happen there).
+.. TODO: Document Rollback or remove it
.. index::
.. _copy:
Using COPY TO and COPY FROM
----------------------------
+===========================
`psycopg3` allows to operate with `PostgreSQL COPY protocol`__. :sql:`COPY` is
one of the most efficient ways to load data into the database (and to modify
:sql:`COPY` command: see :ref:`binary-data` for details and limitations.
-.. index:: async
+.. index:: asyncio
Async operations
================
`~AsyncCursor` supporting an `asyncio` interface.
The design of the asynchronous objects is pretty much the same of the sync
-ones: in order to use them you will only have to scatter the ``async`` keyword
+ones: in order to use them you will only have to scatter the ``await`` keyword
here and there.
.. code:: python
from .errors import DataError, OperationalError, IntegrityError
from .errors import InternalError, ProgrammingError, NotSupportedError
from .connection import AsyncConnection, Connection, Notify
-from .transaction import Rollback
+from .transaction import Rollback, Transaction, AsyncTransaction
from .dbapi20 import BINARY, DATETIME, NUMBER, ROWID, STRING
from .dbapi20 import Binary, Date, DateFromTicks, Time, TimeFromTicks
savepoint_name: Optional[str] = None,
force_rollback: bool = False,
) -> Iterator[Transaction]:
+ """
+ Start a context block with a new transaction or nested transaction.
+
+ :param savepoint_name: Name of the savepoint used to manage a nested
+ transaction. If `!None`, one will be chosen automatically.
+ :param force_rollback: Roll back the transaction at the end of the
+ block even if there were no error (e.g. to try a no-op process).
+ """
with Transaction(self, savepoint_name, force_rollback) as tx:
yield tx
savepoint_name: Optional[str] = None,
force_rollback: bool = False,
) -> AsyncIterator[AsyncTransaction]:
+ """
+ Start a context block with a new transaction or nested transaction.
+ """
tx = AsyncTransaction(self, savepoint_name, force_rollback)
async with tx:
yield tx
)
async def set_client_encoding(self, name: str) -> None:
- """Async version of the `client_encoding` setter."""
+ """Async version of the `~Connection.client_encoding` setter."""
async with self.lock:
self.pgconn.send_query_params(
b"select set_config('client_encoding', $1, false)",
)
async def set_autocommit(self, value: bool) -> None:
- """Async version of the `autocommit` setter."""
+ """Async version of the `~Connection.autocommit` setter."""
async with self.lock:
super()._set_autocommit(value)
@property
def connection(self) -> ConnectionType:
+ """The connection the object is managing."""
return self._conn
@property
def savepoint_name(self) -> Optional[str]:
+ """The name of the savepoint; `None` if handling the main transaction."""
return self._savepoint_name
def __repr__(self) -> str:
class Transaction(BaseTransaction["Connection"]):
+ """
+ Returned by `Connection.transaction()` to handle a transaction block.
+ """
+
def __enter__(self) -> "Transaction":
with self._conn.lock:
self._execute(self._enter_commands())
class AsyncTransaction(BaseTransaction["AsyncConnection"]):
+ """
+ Returned by `AsyncConnection.transaction()` to handle a transaction block.
+ """
+
async def __aenter__(self) -> "AsyncTransaction":
async with self._conn.lock:
await self._execute(self._enter_commands())