From: Daniele Varrazzo Date: Wed, 11 Nov 2020 19:58:37 +0000 (+0000) Subject: Adding an embryo of documentation X-Git-Tag: 3.0.dev0~382 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=ccb5aadb604877546a37c1d5f942bfe51614ebef;p=thirdparty%2Fpsycopg.git Adding an embryo of documentation --- diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 000000000..d4bb2cbb9 --- /dev/null +++ b/docs/Makefile @@ -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 index 000000000..b3190381d --- /dev/null +++ b/docs/conf.py @@ -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 index 000000000..17a7ca925 --- /dev/null +++ b/docs/connection.rst @@ -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 index 000000000..15db99548 --- /dev/null +++ b/docs/from_pg2.rst @@ -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 ` 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 index 000000000..d0d3a7f25 --- /dev/null +++ b/docs/index.rst @@ -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 index 000000000..13019ee9d --- /dev/null +++ b/docs/install.rst @@ -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 index 000000000..c249e989e --- /dev/null +++ b/docs/lib/sql_role.py @@ -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 index 000000000..b46c7a155 --- /dev/null +++ b/docs/requirements.txt @@ -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 index 000000000..efc0034fc --- /dev/null +++ b/docs/usage.rst @@ -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 ` 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,) diff --git a/psycopg3/psycopg3/connection.py b/psycopg3/psycopg3/connection.py index c7bf46a82..abd802127 100644 --- a/psycopg3/psycopg3/connection.py +++ b/psycopg3/psycopg3/connection.py @@ -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)