]> git.ipfire.org Git - thirdparty/psycopg.git/commitdiff
First cut of cursor documentation
authorDaniele Varrazzo <daniele.varrazzo@gmail.com>
Thu, 12 Nov 2020 15:25:49 +0000 (15:25 +0000)
committerDaniele Varrazzo <daniele.varrazzo@gmail.com>
Thu, 12 Nov 2020 17:56:42 +0000 (17:56 +0000)
docs/connection.rst
docs/cursor.rst [new file with mode: 0644]
docs/index.rst
docs/usage.rst
psycopg3/psycopg3/connection.py
psycopg3/psycopg3/cursor.py

index 5ccb9f7a05ff8e15f0da4489ca88825765af3e6f..d0317823f950ab1522825f0b40734324291ca779 100644 (file)
@@ -1,5 +1,5 @@
-The ``Connection`` classes
-==========================
+Connection classes
+==================
 
 .. currentmodule:: psycopg3
 
@@ -12,12 +12,18 @@ usually handles a transaction automatically: other sessions will not be able
 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
@@ -89,13 +95,16 @@ Take a look to :ref:`transactions` for the details.
     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
@@ -108,5 +117,8 @@ Take a look to :ref:`transactions` for the details.
     .. automethod:: set_autocommit
 
 
+Connection support objects
+--------------------------
+
 .. autoclass:: Notify
     :members: channel, payload, pid
diff --git a/docs/cursor.rst b/docs/cursor.rst
new file mode 100644 (file)
index 0000000..281bccb
--- /dev/null
@@ -0,0 +1,142 @@
+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
index d0d3a7f2590107e43ea3bdca6f546f914c251a63..7f841ecac9535891e82e82e00905370672a5fb8a 100644 (file)
@@ -23,6 +23,7 @@ the COPY support.
     usage
     from_pg2
     connection
+    cursor
 
 
 Indices and tables
index 1ee333160c1646984276bd240b2b7dcf4c74f86f..4e86a787cf06b725a271408b5ef0bf12eccf6b51 100644 (file)
@@ -134,10 +134,21 @@ TODO: lift from psycopg2 docs
 Transaction management
 ----------------------
 
-TODO:
+TODO
 
 
 
+.. index::
+    pair: COPY; SQL command
+
+.. _copy:
+
+Using COPY TO and COPY FROM
+---------------------------
+
+TODO
+
+
 .. index:: async
 
 Async operations
index dd1b04f4524e6dc9647fd38ea2c6468630cd29db..7737098e2ed528a58f3de11cbafdcbd4fe17af2c 100644 (file)
@@ -84,7 +84,7 @@ class BaseConnection:
     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 = {}
@@ -99,7 +99,7 @@ class BaseConnection:
 
     @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
@@ -254,7 +254,9 @@ class Connection(BaseConnection):
     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)
 
@@ -381,6 +383,9 @@ class AsyncConnection(BaseConnection):
     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)
 
index ef7d2bc0e8184a80346263c7cfe912d525e834be..18c76afd895588b580ad57069885285b619011c4 100644 (file)
@@ -77,6 +77,7 @@ class Column(Sequence[Any]):
 
     @property
     def name(self) -> str:
+        """The name of the column."""
         rv = self._pgresult.fname(self._index)
         if rv:
             return rv.decode(self._encoding)
@@ -87,10 +88,12 @@ class Column(Sequence[Any]):
 
     @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
@@ -104,11 +107,13 @@ class Column(Sequence[Any]):
 
     @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
@@ -128,6 +133,10 @@ class Column(Sequence[Any]):
 
     @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:
@@ -137,6 +146,7 @@ class Column(Sequence[Any]):
 
     @property
     def null_ok(self) -> Optional[bool]:
+        """Always `None`"""
         return None
 
 
@@ -165,15 +175,18 @@ class BaseCursor:
 
     @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
@@ -184,6 +197,11 @@ class BaseCursor:
 
     @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
@@ -192,6 +210,7 @@ class BaseCursor:
 
     @property
     def rowcount(self) -> int:
+        """Number of records affected by the precedent operation."""
         return self._rowcount
 
     def setinputsizes(self, sizes: Sequence[Any]) -> None:
@@ -203,6 +222,9 @@ class BaseCursor:
         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]
@@ -417,10 +439,16 @@ class Cursor(BaseCursor):
         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()
@@ -433,6 +461,9 @@ class Cursor(BaseCursor):
     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()
@@ -462,10 +493,16 @@ class Cursor(BaseCursor):
         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:
@@ -473,6 +510,11 @@ class Cursor(BaseCursor):
         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
@@ -492,6 +534,9 @@ class Cursor(BaseCursor):
         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]]:
@@ -507,6 +552,9 @@ class Cursor(BaseCursor):
             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()
@@ -641,6 +689,9 @@ class AsyncCursor(BaseCursor):
     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()