]> git.ipfire.org Git - thirdparty/psycopg.git/commitdiff
Adding an embryo of documentation
authorDaniele Varrazzo <daniele.varrazzo@gmail.com>
Wed, 11 Nov 2020 19:58:37 +0000 (19:58 +0000)
committerDaniele Varrazzo <daniele.varrazzo@gmail.com>
Thu, 12 Nov 2020 00:01:17 +0000 (00:01 +0000)
docs/Makefile [new file with mode: 0644]
docs/conf.py [new file with mode: 0644]
docs/connection.rst [new file with mode: 0644]
docs/from_pg2.rst [new file with mode: 0644]
docs/index.rst [new file with mode: 0644]
docs/install.rst [new file with mode: 0644]
docs/lib/sql_role.py [new file with mode: 0644]
docs/requirements.txt [new file with mode: 0644]
docs/usage.rst [new file with mode: 0644]
psycopg3/psycopg3/connection.py

diff --git a/docs/Makefile b/docs/Makefile
new file mode 100644 (file)
index 0000000..d4bb2cb
--- /dev/null
@@ -0,0 +1,20 @@
+# Minimal makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line, and also
+# from the environment for the first two.
+SPHINXOPTS    ?=
+SPHINXBUILD   ?= sphinx-build
+SOURCEDIR     = .
+BUILDDIR      = _build
+
+# Put it first so that "make" without argument is like "make help".
+help:
+       @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+.PHONY: help Makefile
+
+# Catch-all target: route all unknown targets to Sphinx using the new
+# "make mode" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).
+%: Makefile
+       @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
diff --git a/docs/conf.py b/docs/conf.py
new file mode 100644 (file)
index 0000000..b319038
--- /dev/null
@@ -0,0 +1,74 @@
+# Configuration file for the Sphinx documentation builder.
+#
+# This file only contains a selection of the most common options. For a full
+# list see the documentation:
+# https://www.sphinx-doc.org/en/master/usage/configuration.html
+
+# -- Path setup --------------------------------------------------------------
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#
+# import os
+# import sys
+# sys.path.insert(0, os.path.abspath('.'))
+
+import sys
+from pathlib import Path
+
+from better import better_theme_path  # type: ignore
+
+sys.path.append(str(Path(__file__).parent / "lib"))
+
+
+# -- Project information -----------------------------------------------------
+
+project = "psycopg3"
+copyright = "2020, Daniele Varrazzo and The Psycopg Team"
+author = "Daniele Varrazzo"
+release = "UNRELEASED"
+
+
+# -- General configuration ---------------------------------------------------
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = [
+    "sphinx.ext.autodoc",
+    "sphinx.ext.intersphinx",
+    "sql_role",
+]
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ["_templates"]
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+# This pattern also affects html_static_path and html_extra_path.
+exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
+
+
+# -- Options for HTML output -------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  See the documentation for
+# a list of builtin themes.
+#
+html_theme = "better"
+html_theme_path = [better_theme_path]
+html_show_sphinx = False
+html_theme_options = {
+    "linktotheme": False,
+    "cssfiles": ["_static/psycopg.css"],
+}
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ["_static"]
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+default_role = "obj"
+
+intersphinx_mapping = {"py": ("https://docs.python.org/3", None)}
diff --git a/docs/connection.rst b/docs/connection.rst
new file mode 100644 (file)
index 0000000..17a7ca9
--- /dev/null
@@ -0,0 +1,81 @@
+The ``Connection`` classes
+==========================
+
+The `Connection` and `AsyncConnection` classes are the main wrappers for a
+PostgreSQL database session. You can imagine them similar to a :program:`psql`
+session.
+
+One of the differences compared to :program:`psql` is that a `Connection`
+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.
+
+.. autoclass:: psycopg3.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.
+
+    Connections behave as context managers: on block exit, the current
+    transaction will be committed (or rolled back, in case of exception) and
+    the connection will be closed.
+
+    .. automethod:: connect
+
+    Connection parameters can be passed either as a `conninfo string`__ (a
+    ``postgresql://`` url or a list of ``key=value pairs``) or as keywords.
+    Keyword parameters override the ones specified in the connection string.
+
+    .. __: https://www.postgresql.org/docs/current/libpq-connect.html
+        #LIBPQ-CONNSTRING
+
+    This method is also aliased as `psycopg3.connect()`.
+
+    .. seealso::
+
+        - the list of `the accepted connection parameters`__
+        - the `environment varialbes`__ affecting connection
+
+        .. __: https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-PARAMKEYWORDS
+        .. __: https://www.postgresql.org/docs/current/libpq-envars.html
+
+    .. rubric:: Methods you will need every day
+
+    .. automethod:: cursor
+    .. automethod:: commit
+    .. automethod:: rollback
+    .. automethod:: close
+    .. autoproperty:: closed
+
+    .. rubric:: Methods you will need if you do something cool
+
+    .. automethod:: notifies
+
+        Notifies are recevied after using :sql:`LISTEN` in a connection, when
+        any sessions in the database generates a :sql:`NOTIFY` on one of the
+        listened channels.
+
+    .. automethod:: add_notify_handler
+    .. automethod:: remove_notify_handler
+
+    See :ref:`async-notify` for details.
+
+.. autoclass:: psycopg3.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`
+    methods, but have an `async` interface.
+
+    .. automethod:: connect
+    .. automethod:: close
+    .. automethod:: cursor
+    .. automethod:: commit
+    .. automethod:: rollback
+    .. automethod:: notifies
+
+
+.. autoclass:: psycopg3.Notify
diff --git a/docs/from_pg2.rst b/docs/from_pg2.rst
new file mode 100644 (file)
index 0000000..15db995
--- /dev/null
@@ -0,0 +1,65 @@
+.. index::
+    pair: psycopg2; Differences
+
+Differences from psycopg2
+=========================
+
+`!psycopg3` uses the common DBAPI structure of many other database adapter and
+tries to behave as close as possible to `!psycopg2`. There are however a few
+differences to be aware of.
+
+
+Server-side binding
+-------------------
+
+`!psycopg3` sends the query and the parameters to the server separately,
+instead of merging them client-side. PostgreSQL may behave slightly
+differently in this case, usually throwing an error and suggesting to use an
+explicit cast.
+
+.. code:: python
+
+    cur.execute("select '[10,20,30]'::jsonb -> 1").fetchone()
+    # returns (20,)
+
+    cur.execute("select '[10,20,30]'::jsonb -> %s", [1]).fetchone()
+    # raises an exception:
+    # UndefinedFunction: operator does not exist: jsonb -> numeric
+
+    cur.execute("select '[10,20,30]'::jsonb -> %s::int", [1]).fetchone()
+    # returns (20,)
+
+PostgreSQL will also reject the execution of several queries at once
+(separated by semicolon), if they contain parameters. If parameters are used
+you should use distinct `execute()` calls; otherwise you may consider merging
+the query client-side, using `psycopg3.sql` module.
+
+
+Different adaptation system
+---------------------------
+
+The adaptation system has been completely rewritten, in order to address
+server-side parameters adaptation, but also to consider performance,
+flexibility, ease of customization.
+
+Builtin data types should work as expected; if you have wrapped a custom data
+type you should check the `<ref> Adaptation` topic.
+
+
+Other differences
+-----------------
+
+When the connection is used as context manager, at the end of the context the
+connection will be closed. In psycopg2 only the transaction is closed, so a
+connection can be used in several contexts, but the behaviour is surprising
+for people used to several other Python classes wrapping resources, such as
+files.
+
+
+What's new in psycopg3
+======================
+
+- `asyncio` support.
+- Several data types are adapted out-of-the-box: uuid, network, range, bytea,
+  array of any supported type are dealt with automatically.
+- Access to the low-level libpq functions.
diff --git a/docs/index.rst b/docs/index.rst
new file mode 100644 (file)
index 0000000..d0d3a7f
--- /dev/null
@@ -0,0 +1,33 @@
+===================================================
+Psycopg 3 -- PostgreSQL database adapter for Python
+===================================================
+
+`!psycopg3` is a newly designed PostgreSQL_ database adapter for the Python_
+programming language.
+
+`!psycopg3` presents a familiar interface for everyone who has used
+`!psycopg2` or any other `DB API 2.0`__ database adapter, but allows to use
+more modern PostgreSQL and Python features, such as `asyncio` support,
+server-side parameters binding, binary communication, a better integration of
+the COPY support.
+
+.. _Python: https://www.python.org/
+.. _PostgreSQL: https://www.postgresql.org/
+.. __: https://www.python.org/dev/peps/pep-0249/
+
+.. toctree::
+    :maxdepth: 2
+    :caption: Contents:
+
+    install
+    usage
+    from_pg2
+    connection
+
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
diff --git a/docs/install.rst b/docs/install.rst
new file mode 100644 (file)
index 0000000..13019ee
--- /dev/null
@@ -0,0 +1,11 @@
+.. _installation:
+
+Installation
+============
+
+`!psycopg3` is still in a development phase, and hasn't been released yet.
+
+Please refer to `the README`__ for the current installation state, and please know
+that things may change.
+
+.. __: https://github.com/psycopg/psycopg3#readme
diff --git a/docs/lib/sql_role.py b/docs/lib/sql_role.py
new file mode 100644 (file)
index 0000000..c249e98
--- /dev/null
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+"""
+    sql role
+    ~~~~~~~~
+
+    An interpreted text role to style SQL syntax in Psycopg documentation.
+
+    :copyright: Copyright 2010-2020 by Daniele Varrazzo.
+"""
+
+from docutils import nodes, utils
+from docutils.parsers.rst import roles
+
+
+def sql_role(name, rawtext, text, lineno, inliner, options={}, content=[]):
+    text = utils.unescape(text)
+    options["classes"] = ["sql"]
+    return [nodes.literal(rawtext, text, **options)], []
+
+
+def setup(app):
+    roles.register_local_role("sql", sql_role)
diff --git a/docs/requirements.txt b/docs/requirements.txt
new file mode 100644 (file)
index 0000000..b46c7a1
--- /dev/null
@@ -0,0 +1,3 @@
+Sphinx >= 3.3, < 3.4
+docutils >= 0.16, < 0.17
+sphinx-better-theme >= 0.1.5, < 0.2
diff --git a/docs/usage.rst b/docs/usage.rst
new file mode 100644 (file)
index 0000000..efc0034
--- /dev/null
@@ -0,0 +1,220 @@
+.. index::
+    pair: Example; Usage
+
+.. _usage:
+
+Basic module usage
+==================
+
+The basic Psycopg usage is common to all the database adapters implementing
+the `DB API`__ protocol. Here is an interactive session showing some of the
+basic commands:
+
+.. __: https://www.python.org/dev/peps/pep-0249/
+
+.. code:: python
+
+    import psycopg3
+
+    # Connect to an existing database
+    conn = psycopg3.connect("dbname=test user=postgres")
+
+    # Open a cursor to perform database operations
+    cur = conn.cursor()
+
+    # Execute a command: this creates a new table
+    cur.execute("""
+        CREATE TABLE test (
+            id serial PRIMARY KEY,
+            num integer,
+            data text)
+        """)
+
+    # Pass data to fill a query placeholders and let Psycopg perform
+    # the correct conversion (no SQL injections!)
+    cur.execute(
+        "INSERT INTO test (num, data) VALUES (%s, %s)",
+        (100, "abc'def"))
+
+    # Query the database and obtain data as Python objects.
+    cur.execute("SELECT * FROM test")
+    cur.fetchone()
+    # will return (1, 100, "abc'def")
+
+    # You can use `cur.fetchmany()`, `cur.fetchall()` to return a list
+    # of several records, or even iterate on the cursor
+    for record in cur:
+        print(record)
+
+    # Make the changes to the database persistent
+    conn.commit()
+
+    # Close communication with the database
+    cur.close()
+    conn.close()
+
+
+Note that the `cursor.execute()` method returns the cursor itself, so the
+`fetch*()` methods can be appended right after it.
+
+.. code:: python
+
+    cur.execute("SELECT * FROM test").fetchone()
+
+    for record in cur.execute("SELECT * FROM test"):
+        print(record)
+
+
+The connections and cursors act as context managers, so you can run:
+
+.. code:: python
+
+    with psycopg3.connect("dbname=test user=postgres") as conn:
+        with conn.cursor() as cur:
+            cur.execute(
+                "INSERT INTO test (num, data) VALUES (%s, %s)",
+                (100, "abc'def"))
+            cur.execute("SELECT * FROM test").fetchone()
+            # will return (1, 100, "abc'def")
+
+        # the cursor is closed upon leaving the context
+
+    # the transaction is committed on successful exit of the context
+    # and the connection closed
+
+
+If you are working in an `asyncio` project you can use a very similar pattern:
+
+.. code:: python
+
+    async with await psycopg3.AsyncConnection.connect(
+            "dbname=test user=postgres") as aconn:
+        async with await aconn.cursor() as acur:
+            await acur.execute(
+                "INSERT INTO test (num, data) VALUES (%s, %s)",
+                (100, "abc'def"))
+            await acur.execute("SELECT * FROM test")
+            await acur.fetchone()
+            # will return (1, 100, "abc'def")
+
+
+The main entry points of Psycopg are:
+
+- The function `~psycopg3.connect()` creates a new database session and
+  returns a new `connection` instance. `psycopg3.AsyncConnection.connect()`
+  creates an asyncio connection instead.
+
+- The `connection` class encapsulates a database session. It allows to:
+
+  - create new `cursor` instances using the `~connection.cursor()` method to
+    execute database commands and queries,
+
+  - terminate transactions using the methods `~connection.commit()` or
+    `~connection.rollback()`.
+
+- The class `cursor` allows interaction with the database:
+
+  - send commands to the database using methods such as `~cursor.execute()`
+    and `~cursor.executemany()`,
+
+  - retrieve data from the database :ref:`by iteration <cursor-iterable>` or
+    using methods such as `~cursor.fetchone()`, `~cursor.fetchmany()`,
+    `~cursor.fetchall()`.
+
+
+.. index::
+    pair: Query; Parameters
+
+.. _query-parameters:
+
+Passing parameters to SQL queries
+---------------------------------
+
+TODO: lift from psycopg2 docs
+
+
+.. _transactions:
+
+Transaction management
+----------------------
+
+TODO:
+
+
+.. index::
+    pair: Asynchronous; Notifications
+    pair: LISTEN; SQL command
+    pair: NOTIFY; SQL command
+
+.. _async-notify:
+
+Asynchronous notifications
+--------------------------
+
+Psycopg allows asynchronous interaction with other database sessions using the
+facilities offered by PostgreSQL commands |LISTEN|_ and |NOTIFY|_. Please
+refer to the PostgreSQL documentation for examples about how to use this form
+of communication.
+
+.. |LISTEN| replace:: :sql:`LISTEN`
+.. _LISTEN: https://www.postgresql.org/docs/current/static/sql-listen.html
+.. |NOTIFY| replace:: :sql:`NOTIFY`
+.. _NOTIFY: https://www.postgresql.org/docs/current/static/sql-notify.html
+
+Because of the way sessions interact with notifications (see |NOTIFY|_
+documentation), you should keep the connection in `~connection.autocommit`
+mode if you wish to receive or send notifications in a timely manner.
+
+Notifications are received as instances of `~psycopg3.Notify`. If you are
+reserving a connection only to receive notifications, the simplest way is to
+consume the `~psycopg3.Connection.notifies` generator. The generator can be
+stopped using ``close()``. The following example will print notifications and
+stop when one containing the ``stop`` message is received.
+
+.. code:: python
+
+    import psycopg3
+    conn = psycopg3.connect("", autocommit=True)
+    conn.cursor().execute("LISTEN mychan")
+    gen = conn.notifies()
+    for notify in gen:
+        print(notify)
+        if notify.payload == "stop":
+            gen.close()
+    print("there, I stopped")
+
+If you run some :sql:`NOTIFY` in a :program:`psql` session:
+
+.. code:: psql
+
+    =# notify mychan, 'hello';
+    NOTIFY
+    =# notify mychan, 'hey';
+    NOTIFY
+    =# notify mychan, 'stop';
+    NOTIFY
+
+You may get output from the Python process such as::
+
+    Notify(channel='mychan', payload='hello', pid=961823)
+    Notify(channel='mychan', payload='hey', pid=961823)
+    Notify(channel='mychan', payload='stop', pid=961823)
+    there, I stopped
+
+Alternatively, you can use `~psycopg3.Connection.add_notify_handler()` to
+register a callback function, which will be invoked whenever a notification is
+received, during the normal query processing; you will be then able to use the
+connection normally. Please note that in this case notifications will not be
+received immediately, but only during a connection operation, such as a query.
+
+.. code:: python
+
+    conn.add_notify_handler(lambda n: print(f"got this: {n}"))
+
+    # meanwhile in psql...
+    # =# notify mychan, 'hey';
+    # NOTIFY
+
+    print(conn.cursor().execute("select 1").fetchone())
+    # got this: Notify(channel='mychan', payload='hey', pid=961823)
+    # (1,)
index c7bf46a8299d6d4620ef21fcd737f38560288a32..abd8021274ec917dcb293fe084ce16157905305e 100644 (file)
@@ -42,6 +42,8 @@ else:
 
 
 class Notify(NamedTuple):
+    """An asynchronous notification received from the database."""
+
     channel: str
     payload: str
     pid: int
@@ -95,6 +97,7 @@ class BaseConnection:
 
     @property
     def closed(self) -> bool:
+        """`true` if the connection is closed."""
         return self.status == self.ConnStatus.BAD
 
     @property
@@ -182,9 +185,15 @@ class BaseConnection:
                 )
 
     def add_notify_handler(self, callback: NotifyHandler) -> None:
+        """
+        Register a callable to be invoked whenever a notification is received.
+        """
         self._notify_handlers.append(callback)
 
     def remove_notify_handler(self, callback: NotifyHandler) -> None:
+        """
+        Unregister a notification callable previously registered.
+        """
         self._notify_handlers.remove(callback)
 
     @staticmethod
@@ -206,9 +215,7 @@ class BaseConnection:
 
 class Connection(BaseConnection):
     """
-    Wrap a connection to the database.
-
-    This class implements a DBAPI-compliant interface.
+    Wrapper for a connection to the database.
     """
 
     cursor_factory: Type[cursor.Cursor]
@@ -252,11 +259,13 @@ class Connection(BaseConnection):
         self.close()
 
     def close(self) -> None:
+        """Close the database connection."""
         self.pgconn.finish()
 
     def cursor(
         self, name: str = "", format: pq.Format = pq.Format.TEXT
     ) -> cursor.Cursor:
+        """Return a new cursor to send commands and query the connection."""
         cur = self._cursor(name, format=format)
         return cast(cursor.Cursor, cur)
 
@@ -277,10 +286,12 @@ class Connection(BaseConnection):
             )
 
     def commit(self) -> None:
+        """Commit any pending transaction to the database."""
         with self.lock:
             self._exec_commit_rollback(b"commit")
 
     def rollback(self) -> None:
+        """Roll back to the start of any pending transaction."""
         with self.lock:
             self._exec_commit_rollback(b"rollback")
 
@@ -335,10 +346,7 @@ class Connection(BaseConnection):
 
 class AsyncConnection(BaseConnection):
     """
-    Wrap an asynchronous connection to the database.
-
-    This class implements a DBAPI-inspired interface, with all the blocking
-    methods implemented as coroutines.
+    Asynchronous wrapper for a connection to the database.
     """
 
     cursor_factory: Type[cursor.AsyncCursor]
@@ -352,7 +360,6 @@ class AsyncConnection(BaseConnection):
     async def connect(
         cls, conninfo: str = "", *, autocommit: bool = False, **kwargs: Any
     ) -> "AsyncConnection":
-        """`asyncio` version of `~Connection.connect()`."""
         conninfo = make_conninfo(conninfo, **kwargs)
         gen = connect(conninfo)
         pgconn = await cls.wait(gen)