-The ``Connection`` classes
-==========================
+Connection classes
+==================
.. currentmodule:: psycopg3
to see the changes until you have committed them, more or less explicitly.
Take a look to :ref:`transactions` for the details.
+
+The `!Connection` class
+-----------------------
+
.. autoclass:: Connection
- This class implements a DBAPI-compliant interface. It is what you want to
- use if you write a "classic", blocking program (eventually using threads or
- Eventlet/gevent for concurrency. If your program uses `asyncio` you might
- want to use `AsyncConnection` instead.
+ This class implements a `DBAPI-compliant interface`__. It is what you want
+ to use if you write a "classic", blocking program (eventually using
+ threads or Eventlet/gevent for concurrency. If your program uses `asyncio`
+ you might want to use `AsyncConnection` instead.
+
+ .. __: https://www.python.org/dev/peps/pep-0249/#connection-objects
Connections behave as context managers: on block exit, the current
transaction will be committed (or rolled back, in case of exception) and
TODO: document `Diagnostic`
+The `!AsyncConnection` class
+----------------------------
+
.. autoclass:: AsyncConnection
This class implements a DBAPI-inspired interface, with all the blocking
methods implemented as coroutines. Unless specified otherwise,
non-blocking methods are shared with the `Connection` class.
- The following methods have the same behaviour of the matching `~Connection`
+ The following methods have the same behaviour of the matching `!Connection`
methods, but have an `async` interface.
.. automethod:: connect
.. automethod:: set_autocommit
+Connection support objects
+--------------------------
+
.. autoclass:: Notify
:members: channel, payload, pid
--- /dev/null
+Cursor classes
+==============
+
+.. currentmodule:: psycopg3
+
+The `Cursor` and `AsyncCursor` classes are the main objects to send commands
+to a PostgreSQL database session. They are normally created by the
+`~Connection.cursor()` method.
+
+A `Connection` can create several cursors, but only one at time can perform
+operations, so they are not the best way to achieve parallelism (you may want
+to operate with several connections instead). All the cursors on the same
+connection have a view of the same session, so they can see each other's
+uncommitted data.
+
+
+The `!Cursor` class
+-------------------
+
+.. autoclass:: Cursor
+
+ This class implements `DBAPI-compliant interface`__. It is what the
+ classic `Connection.cursor()` method returns. `AsyncConnection.cursor()`
+ will create instead `AsyncCursor` objects, which have the same set of
+ method but expose an `asyncio` interface and require ``async`` and
+ ``await`` keywords to operate.
+
+ .. __: https://www.python.org/dev/peps/pep-0249/#cursor-objects
+
+ Cursors behave as context managers: on block exit they are closed and
+ further operation will not be possible. Closing a cursor will not
+ terminate a transaction or a session though.
+
+ .. automethod:: close
+ .. autoproperty:: closed
+
+ .. rubric:: Methods to send commands
+
+ .. automethod:: execute
+
+ Return the cursor itself, so that it will be possible to chain a fetch
+ operation after the call
+
+ See :ref:`query-parameters` for all the details about executing
+ queries.
+
+ .. automethod:: executemany
+
+ This is more efficient than performing separate queries, but in case of
+ several :sql:`INSERT` (and with some SQL creativity for massive
+ :sql:`UPDATE` too) you may consider using `copy()`.
+
+ See :ref:`query-parameters` for all the details about executing
+ queries.
+
+ .. automethod:: copy
+
+ See :ref:`copy` for information about :sql:`COPY`.
+
+ .. automethod:: callproc
+
+ This method exists for DBAPI compatibility but it's not much different
+ than calling `execute()` on a :sql:`SELECT myproc(%s, %s, ...)`, which
+ will give you more flexibility in passing arguments and retrieving
+ results. Don't bother...
+
+ .. rubric:: Methods to retrieve results
+
+ Fetch methods are only available if the last operation produced results,
+ e.g. a :sql:`SELECT` or a command with :sql:`RETURNING`. They will raise
+ an exception if used with operations that don't return result, such as an
+ :sql:`INSERT` with no :sql:`RETURNING` or an :sql:`ALTER TABLE`.
+
+ Cursors are iterable objects, so just using ``for var in cursor`` syntax
+ will iterate on the records in the current recordset.
+
+ .. automethod:: fetchone
+ .. automethod:: fetchmany
+ .. automethod:: fetchall
+ .. automethod:: nextset
+ .. autoattribute:: pgresult
+
+ .. rubric:: Information about the data
+
+ .. autoproperty:: description
+ .. autoproperty:: rowcount
+
+
+The `!AsyncCursor` class
+------------------------
+
+.. autoclass:: AsyncCursor
+
+ This class implements a DBAPI-inspired interface, with all the blocking
+ methods implemented as coroutines. Unless specified otherwise,
+ non-blocking methods are shared with the `Cursor` class.
+
+ The following methods have the same behaviour of the matching `!Cursor`
+ methods, but have an `async` interface.
+
+ .. automethod:: close
+ .. automethod:: execute
+ .. automethod:: executemany
+ .. automethod:: copy
+ .. automethod:: callproc
+ .. automethod:: fetchone
+ .. automethod:: fetchmany
+ .. automethod:: fetchall
+
+
+Cursor support objects
+----------------------
+
+.. autoclass:: Column
+
+ An object describing a column of data from a database result, `as described
+ by the DBAPI`__, so it can also be unpacked as a 7-items tuple
+
+ .. __: https://www.python.org/dev/peps/pep-0249/#description
+
+ .. autoproperty:: name
+ .. autoproperty:: type_code
+ .. autoproperty:: display_size
+ .. autoproperty:: internal_size
+ .. autoproperty:: precision
+ .. autoproperty:: scale
+
+
+.. autoclass:: Copy
+
+ .. automethod:: read
+ .. automethod:: write
+ .. automethod:: write_row
+ .. automethod:: finish
+
+
+.. autoclass:: AsyncCopy
+
+ .. automethod:: read
+ .. automethod:: write
+ .. automethod:: write_row
+ .. automethod:: finish
usage
from_pg2
connection
+ cursor
Indices and tables
Transaction management
----------------------
-TODO:
+TODO
+.. index::
+ pair: COPY; SQL command
+
+.. _copy:
+
+Using COPY TO and COPY FROM
+---------------------------
+
+TODO
+
+
.. index:: async
Async operations
TransactionStatus = pq.TransactionStatus
def __init__(self, pgconn: pq.proto.PGconn):
- self.pgconn = pgconn
+ self.pgconn = pgconn # TODO: document this
self.cursor_factory = cursor.BaseCursor
self._autocommit = False
self.dumpers: proto.DumpersMap = {}
@property
def closed(self) -> bool:
- """`true` if the connection is closed."""
+ """`True` if the connection is closed."""
return self.pgconn.status == self.ConnStatus.BAD
@property
def cursor(
self, name: str = "", format: pq.Format = pq.Format.TEXT
) -> cursor.Cursor:
- """Return a new cursor to send commands and query the connection."""
+ """
+ Return a new `Cursor` to send commands and queries to the connection.
+ """
cur = self._cursor(name, format=format)
return cast(cursor.Cursor, cur)
async def cursor(
self, name: str = "", format: pq.Format = pq.Format.TEXT
) -> cursor.AsyncCursor:
+ """
+ Return a new `AsyncCursor` to send commands and queries to the connection.
+ """
cur = self._cursor(name, format=format)
return cast(cursor.AsyncCursor, cur)
@property
def name(self) -> str:
+ """The name of the column."""
rv = self._pgresult.fname(self._index)
if rv:
return rv.decode(self._encoding)
@property
def type_code(self) -> int:
+ """The numeric OID of the column."""
return self._pgresult.ftype(self._index)
@property
def display_size(self) -> Optional[int]:
+ """The field size, for :sql:`varchar(n)`, None otherwise."""
t = builtins.get(self.type_code)
if not t:
return None
@property
def internal_size(self) -> Optional[int]:
+ """The interal field size for fixed-size types, None otherwise."""
fsize = self._pgresult.fsize(self._index)
return fsize if fsize >= 0 else None
@property
def precision(self) -> Optional[int]:
+ """The number of digits for fixed precision types."""
t = builtins.get(self.type_code)
if not t:
return None
@property
def scale(self) -> Optional[int]:
+ """The number of digits after the decimal point if available.
+
+ TODO: probably better than precision for datetime objects? review.
+ """
if self.type_code == builtins["numeric"].oid:
fmod = self._pgresult.fmod(self._index) - 4
if fmod >= 0:
@property
def null_ok(self) -> Optional[bool]:
+ """Always `None`"""
return None
@property
def closed(self) -> bool:
+ """`True` if the cursor is closed."""
return self._closed
@property
def status(self) -> Optional[pq.ExecStatus]:
+ # TODO: do we want this?
res = self.pgresult
return res.status if res else None
@property
def pgresult(self) -> Optional[pq.proto.PGresult]:
+ """The `~psycopg3.pq.PGresult` exposed by the cursor."""
return self._pgresult
@pgresult.setter
@property
def description(self) -> Optional[List[Column]]:
+ """
+ A list of `Column` object describing the current resultset.
+
+ `None` if the current resulset didn't return tuples.
+ """
res = self.pgresult
if not res or res.status != self.ExecStatus.TUPLES_OK:
return None
@property
def rowcount(self) -> int:
+ """Number of records affected by the precedent operation."""
return self._rowcount
def setinputsizes(self, sizes: Sequence[Any]) -> None:
pass
def nextset(self) -> Optional[bool]:
+ """
+ Move to the next result set if `execute()` returned more than one.
+ """
self._iresult += 1
if self._iresult < len(self._results):
self.pgresult = self._results[self._iresult]
self.close()
def close(self) -> None:
+ """
+ Close the current cursor and free associated resources.
+ """
self._closed = True
self._reset()
def execute(self, query: Query, vars: Optional[Params] = None) -> "Cursor":
+ """
+ Execute a query or command to the database.
+ """
with self.connection.lock:
self._start_query()
self.connection._start_query()
def executemany(
self, query: Query, vars_seq: Sequence[Params]
) -> "Cursor":
+ """
+ Execute the same command with a sequence of input data.
+ """
with self.connection.lock:
self._start_query()
self.connection._start_query()
args: Optional[Params] = None,
kwargs: Optional[Mapping[str, Any]] = None,
) -> Optional[Params]:
+ """
+ Call a stored procedure to the database.
+ """
self.execute(self._callproc_sql(name, args))
return args
def fetchone(self) -> Optional[Sequence[Any]]:
+ """
+ Return the next record from the current recordset, `None` if not available.
+ """
self._check_result()
rv = self._transformer.load_row(self._pos)
if rv is not None:
return rv
def fetchmany(self, size: int = 0) -> List[Sequence[Any]]:
+ """
+ Return the next *size* records from the current recordset.
+
+ *size* default to `!self.arraysize` if not specified.
+ """
self._check_result()
if not size:
size = self.arraysize
return rv
def fetchall(self) -> List[Sequence[Any]]:
+ """
+ Return all the remaining records from the current recordset.
+ """
return list(self)
def __iter__(self) -> Iterator[Sequence[Any]]:
yield row
def copy(self, statement: Query, vars: Optional[Params] = None) -> Copy:
+ """
+ Initiate a :sql:`COPY` operation and return a `Copy` object to manage it.
+ """
with self.connection.lock:
self._start_query()
self.connection._start_query()
async def copy(
self, statement: Query, vars: Optional[Params] = None
) -> AsyncCopy:
+ """
+ Initiate a :sql:`COPY` operation and return an `AsyncCopy` object.
+ """
async with self.connection.lock:
self._start_query()
await self.connection._start_query()