From 3ae7b670de02d1ba7cb8a57ae35906b6e8f26e65 Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Sun, 22 Nov 2020 16:16:12 +0000 Subject: [PATCH] Api docs and typing system made consistent Top-level objects are annotated as coming from the main package: this way the documentation can link them consistently to where they are documented (e.g. `Connection` is `psycopg3.Connection`, not `psycopg3.connection.Connection`) and MyPy is happy too. --- docs/connection.rst | 6 ++-- docs/cursor.rst | 60 ++++++++++++++++++++++---------- psycopg3/psycopg3/__init__.py | 18 ++++++++++ psycopg3/psycopg3/connection.py | 20 ++++++----- psycopg3/psycopg3/cursor.py | 11 +++--- psycopg3/psycopg3/transaction.py | 4 ++- 6 files changed, 81 insertions(+), 38 deletions(-) diff --git a/docs/connection.rst b/docs/connection.rst index 0f76c1b96..38cfed981 100644 --- a/docs/connection.rst +++ b/docs/connection.rst @@ -70,7 +70,7 @@ The `!Connection` class .. automethod:: rollback() .. automethod:: transaction(savepoint_name: Optional[str] = None, force_rollback: bool = False) -> Transaction - It must be called as ``with conn.transaction() as tx: ...`` + .. note:: it must be called as ``with conn.transaction() as tx: ...`` Inside a transaction block it will not be possible to call `commit()` or `rollback()`. @@ -147,7 +147,7 @@ The `!AsyncConnection` class .. automethod:: transaction(savepoint_name: Optional[str] = None, force_rollback: bool = False) -> AsyncTransaction - It must be called as ``async with conn.transaction() as tx: ...``. + .. note:: it must be called as ``async with conn.transaction() as tx: ...``. .. automethod:: notifies .. automethod:: set_client_encoding @@ -160,7 +160,7 @@ Connection support objects .. autoclass:: Notify() :members: channel, payload, pid - The objet is usually returned by `Connection.notifies()`. + The object is usually returned by `Connection.notifies()`. .. rubric:: Objects involved in :ref:`transactions` diff --git a/docs/cursor.rst b/docs/cursor.rst index 83a24f1ec..c60bb522e 100644 --- a/docs/cursor.rst +++ b/docs/cursor.rst @@ -31,6 +31,11 @@ The `!Cursor` class further operation will not be possible. Closing a cursor will not terminate a transaction or a session though. + .. attribute:: connection + :type: Connection + + The connection this cursor is using. + .. automethod:: close .. note:: you can use :ref:`with conn.cursor(): ...` @@ -41,15 +46,25 @@ The `!Cursor` class .. rubric:: Methods to send commands - .. automethod:: execute + .. automethod:: execute(query: Query, vars: Optional[Args]=None) -> Cursor + + :param query: The query to execute + :type query: `!str`, `!bytes`, or `sql.Composable` + :param vars: The parameters to pass to the query, if any + :type vars: Mapping, Sequence Return the cursor itself, so that it will be possible to chain a fetch - operation after the call + operation after the call. See :ref:`query-parameters` for all the details about executing queries. - .. automethod:: executemany + .. automethod:: executemany(query: Query, vars_seq: Sequence[Args]) -> Cursor + + :param query: The query to execute + :type query: `!str`, `!bytes`, or `sql.Composable` + :param vars_seq: The parameters to pass to the query + :type vars_seq: Sequence of Mapping or Sequence This is more efficient than performing separate queries, but in case of several :sql:`INSERT` (and with some SQL creativity for massive @@ -58,18 +73,16 @@ The `!Cursor` class See :ref:`query-parameters` for all the details about executing queries. - .. automethod:: copy - - It must be called as ``with cur.copy() as copy: ...`` + .. automethod:: copy(statement: Query, vars: Optional[Args]=None) -> Copy - See :ref:`copy` for information about :sql:`COPY`. + :param statement: The copy operation to execute + :type statement: `!str`, `!bytes`, or `sql.Composable` + :param args: The parameters to pass to the query, if any + :type args: Mapping, Sequence - .. automethod:: callproc + .. note:: it must be called as ``with cur.copy() as copy: ...`` - 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... + See :ref:`copy` for information about :sql:`COPY`. .. rubric:: Methods to retrieve results @@ -89,8 +102,15 @@ The `!Cursor` class .. rubric:: Information about the data - .. autoproperty:: description - .. autoproperty:: rowcount + .. attribute:: description + :type: Optional[List[Column]] + + A list of objects describing each column of the current queryset. + + `!None` if the last operation didn't return a queryset. + + .. autoattribute:: rowcount + :annotation: int The `!AsyncCursor` class @@ -105,19 +125,21 @@ The `!AsyncCursor` class The following methods have the same behaviour of the matching `!Cursor` methods, but should be called using the `await` keyword. + .. attribute:: connection + :type: AsyncConnection + .. automethod:: close .. note:: you can use ``async with`` to close the cursor automatically when the block is exited, but be careful about the async quirkness: see :ref:`with-statement` for details. - .. automethod:: execute - .. automethod:: executemany - .. automethod:: copy + .. automethod:: execute(query: Query, vars: Optional[Args]=None) -> AsyncCursor + .. automethod:: executemany(query: Query, vars_seq: Sequence[Args]) -> AsyncCursor + .. automethod:: copy(statement: Query, vars: Optional[Args]=None) -> AsyncCopy - It must be called as ``async with cur.copy() as copy: ...`` + .. note:: it must be called as ``async with cur.copy() as copy: ...`` - .. automethod:: callproc .. automethod:: fetchone .. automethod:: fetchmany .. automethod:: fetchall diff --git a/psycopg3/psycopg3/__init__.py b/psycopg3/psycopg3/__init__.py index 13067887d..c10859f52 100644 --- a/psycopg3/psycopg3/__init__.py +++ b/psycopg3/psycopg3/__init__.py @@ -34,3 +34,21 @@ if pq.__impl__ == "c": from psycopg3_c import _psycopg3 _psycopg3.register_builtin_c_loaders() + + +# Note: defining the exported methods helps both Sphynx in documenting that +# this is the canonical place to obtain them and should be used by MyPy too, +# so that function signatures are consistent with the documentation. +__all__ = [ + "AsyncConnection", + "AsyncCopy", + "AsyncCursor", + "AsyncTransaction", + "Column", + "Connection", + "Copy", + "Cursor", + "Notify", + "Rollback", + "Transaction", +] diff --git a/psycopg3/psycopg3/connection.py b/psycopg3/psycopg3/connection.py index e308b6b92..0d8bfe4c8 100644 --- a/psycopg3/psycopg3/connection.py +++ b/psycopg3/psycopg3/connection.py @@ -39,8 +39,8 @@ connect: Callable[[str], PQGen["PGconn"]] execute: Callable[["PGconn"], PQGen[List["PGresult"]]] if TYPE_CHECKING: + import psycopg3 from .pq.proto import PGconn, PGresult - from .cursor import Cursor, AsyncCursor if pq.__impl__ == "c": from psycopg3_c import _psycopg3 @@ -69,7 +69,7 @@ class Notify(NamedTuple): NoticeHandler = Callable[[e.Diagnostic], None] -NotifyHandler = Callable[[Notify], None] +NotifyHandler = Callable[["psycopg3.Notify"], None] class BaseConnection: @@ -96,7 +96,9 @@ class BaseConnection: ConnStatus = pq.ConnStatus TransactionStatus = pq.TransactionStatus - cursor_factory: Union[Type["Cursor"], Type["AsyncCursor"]] + cursor_factory: Union[ + Type["psycopg3.Cursor"], Type["psycopg3.AsyncCursor"] + ] def __init__(self, pgconn: "PGconn"): self.pgconn = pgconn # TODO: document this @@ -227,7 +229,7 @@ class Connection(BaseConnection): Wrapper for a connection to the database. """ - cursor_factory: Type[cursor.Cursor] + cursor_factory: Type["psycopg3.Cursor"] def __init__(self, pgconn: "PGconn"): super().__init__(pgconn) @@ -273,7 +275,7 @@ class Connection(BaseConnection): def cursor( self, name: str = "", format: pq.Format = pq.Format.TEXT - ) -> cursor.Cursor: + ) -> "psycopg3.Cursor": """ Return a new `Cursor` to send commands and queries to the connection. """ @@ -369,7 +371,7 @@ class Connection(BaseConnection): result, encoding=self.client_encoding ) - def notifies(self) -> Iterator[Notify]: + def notifies(self) -> Iterator["psycopg3.Notify"]: """ Yield `Notify` objects as soon as they are received from the database. """ @@ -393,7 +395,7 @@ class AsyncConnection(BaseConnection): Asynchronous wrapper for a connection to the database. """ - cursor_factory: Type[cursor.AsyncCursor] + cursor_factory: Type["psycopg3.AsyncCursor"] def __init__(self, pgconn: "PGconn"): super().__init__(pgconn) @@ -432,7 +434,7 @@ class AsyncConnection(BaseConnection): async def cursor( self, name: str = "", format: pq.Format = pq.Format.TEXT - ) -> cursor.AsyncCursor: + ) -> "psycopg3.AsyncCursor": """ Return a new `AsyncCursor` to send commands and queries to the connection. """ @@ -528,7 +530,7 @@ class AsyncConnection(BaseConnection): result, encoding=self.client_encoding ) - async def notifies(self) -> AsyncIterator[Notify]: + async def notifies(self) -> AsyncIterator["psycopg3.Notify"]: while 1: async with self.lock: ns = await self.wait(notifies(self.pgconn)) diff --git a/psycopg3/psycopg3/cursor.py b/psycopg3/psycopg3/cursor.py index c66b50c5d..c08d1ed33 100644 --- a/psycopg3/psycopg3/cursor.py +++ b/psycopg3/psycopg3/cursor.py @@ -154,7 +154,7 @@ class Column(Sequence[Any]): @property def null_ok(self) -> Optional[bool]: - """Always `None`""" + """Always `!None`""" return None @@ -509,7 +509,9 @@ class Cursor(BaseCursor["Connection"]): def fetchone(self) -> Optional[Sequence[Any]]: """ - Return the next record from the current recordset, `None` if not available. + Return the next record from the current recordset. + + Return `!None` the recordset is finished. """ self._check_result() rv = self._transformer.load_row(self._pos) @@ -564,7 +566,7 @@ class Cursor(BaseCursor["Connection"]): self, statement: Query, vars: Optional[Params] = None ) -> Iterator[Copy]: """ - Initiate a :sql:`COPY` operation and return a `Copy` object to manage it. + Initiate a :sql:`COPY` operation and return an object to manage it. """ with self._start_copy(statement, vars) as copy: yield copy @@ -698,9 +700,6 @@ class AsyncCursor(BaseCursor["AsyncConnection"]): async def copy( self, statement: Query, vars: Optional[Params] = None ) -> AsyncIterator[AsyncCopy]: - """ - Initiate a :sql:`COPY` operation and return an `AsyncCopy` object. - """ copy = await self._start_copy(statement, vars) async with copy: yield copy diff --git a/psycopg3/psycopg3/transaction.py b/psycopg3/psycopg3/transaction.py index 30b482c95..b42ed81ea 100644 --- a/psycopg3/psycopg3/transaction.py +++ b/psycopg3/psycopg3/transaction.py @@ -57,7 +57,9 @@ class BaseTransaction(Generic[ConnectionType]): @property def savepoint_name(self) -> Optional[str]: - """The name of the savepoint; `None` if handling the main transaction.""" + """ + The name of the savepoint; `!None` if handling the main transaction. + """ # Yes, it may change on __enter__. No, I don't care, because the # un-entered state is outside the public interface. return self._savepoint_name -- 2.47.2