]> git.ipfire.org Git - thirdparty/psycopg.git/commitdiff
Add typing docs page
authorDaniele Varrazzo <daniele.varrazzo@gmail.com>
Tue, 12 Oct 2021 14:05:47 +0000 (16:05 +0200)
committerDaniele Varrazzo <daniele.varrazzo@gmail.com>
Tue, 12 Oct 2021 14:06:25 +0000 (16:06 +0200)
docs/advanced/index.rst
docs/advanced/rows.rst
docs/advanced/typing.rst [new file with mode: 0644]
docs/basic/from_pg2.rst
docs/index.rst

index f8d6f831799a58fa297a44c3ff36defb69f60f7b..a554ae2393733a71cab446b39dd1c7663541d711 100644 (file)
@@ -12,6 +12,7 @@ usages.
     :caption: Contents:
 
     async
+    typing
     rows
     pool
     cursors
index cf6d35c3875cb9f0d76548da4f3768c664a52b50..581a68da68fd25bdf0a9f452a6d3e6ad8b82ad5c 100644 (file)
@@ -114,103 +114,3 @@ These can then be used by specifying a `row_factory` argument in
     cur = conn.execute("SELECT first_name, last_name, age FROM persons")
     person = cur.fetchone()
     print(f"{person['first_name']} {person['last_name']}")
-
-
-.. _row-factory-static:
-
-Use with a static analyzer
---------------------------
-
-The `~psycopg.Connection` and `~psycopg.Cursor` classes are `generic types`__:
-the parameter `!Row` is passed by the ``row_factory`` argument (of the
-`~Connection.connect()` and the `~Connection.cursor()` method) and it controls
-what type of record is returned by the fetch methods of the cursors. The
-default `~psycopg.rows.tuple_row` returns a generic tuple as return type
-(`Tuple[Any, ...]`). This information can be used for type checking using a
-static analyzer such as mypy_.
-
-.. _mypy: https://mypy.readthedocs.io/
-.. __: https://mypy.readthedocs.io/en/stable/generics.html
-
-.. code:: python
-
-   conn = psycopg.connect()
-   # conn type is psycopg.Connection[Tuple[Any, ...]]
-
-   dconn = psycopg.connect(row_factory=dict_row)
-   # dconn type is psycopg.Connection[Dict[str, Any]]
-
-   cur = conn.cursor()
-   # cur type is psycopg.Cursor[Tuple[Any, ...]]
-
-   dcur = conn.cursor(row_factory=dict_row)
-   dcur = dconn.cursor()
-   # dcur type is psycopg.Cursor[Dict[str, Any]] in both cases
-
-   rec = cur.fetchone()
-   # rec type is Optional[Tuple[Any, ...]]
-
-   drec = dcur.fetchone()
-   # drec type is Optional[Dict[str, Any]]
-
-
-Example: returning records as Pydantic models
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Using Pydantic_ it is possible to enforce static typing at runtime. Using a
-Pydantic model factory the code can be checked statically using mypy and
-querying the database will raise an exception if the rows returned is not
-compatible with the model.
-
-.. _Pydantic: https://pydantic-docs.helpmanual.io/
-
-The following example can be checked with ``mypy --strict`` without reporting
-any issue. Pydantic will also raise a runtime error in case the
-`!Person` is used with a query that returns incompatible data.
-
-.. code:: python
-
-    from datetime import date
-    from typing import Optional
-
-    import psycopg
-    from psycopg.rows import class_row
-    from pydantic import BaseModel
-
-    class Person(BaseModel):
-        id: int
-        first_name: str
-        last_name: str
-        dob: Optional[date]
-
-    def fetch_person(id: int) -> Person:
-        with psycopg.connect() as conn:
-            with conn.cursor(row_factory=class_row(Person)) as cur:
-                cur.execute(
-                    """
-                    SELECT id, first_name, last_name, dob
-                    FROM (VALUES
-                        (1, 'John', 'Doe', '2000-01-01'::date),
-                        (2, 'Jane', 'White', NULL)
-                    ) AS data (id, first_name, last_name, dob)
-                    WHERE id = %(id)s;
-                    """,
-                    {"id": id},
-                )
-                obj = cur.fetchone()
-
-                # reveal_type(obj) would return 'Optional[Person]' here
-
-                if not obj:
-                    raise KeyError(f"person {id} not found")
-
-                # reveal_type(obj) would return 'Person' here
-
-                return obj
-
-    for id in [1, 2]:
-        p = fetch_person(id)
-        if p.dob:
-            print(f"{p.first_name} was born in {p.dob.year}")
-        else:
-            print(f"Who knows when {p.first_name} was born")
diff --git a/docs/advanced/typing.rst b/docs/advanced/typing.rst
new file mode 100644 (file)
index 0000000..9b58372
--- /dev/null
@@ -0,0 +1,128 @@
+.. currentmodule:: psycopg
+
+.. _static-typing:
+
+Static Typing
+=============
+
+Psycopg source code is annotated according to :pep:`0484` type hints and is
+checked using the current version of Mypy_ in ``--strict`` mode.
+
+If your application is checked using Mypy too you can make use of Psycopg
+types to validate the correct use of Psycopg objects and of the data returned
+by the database.
+
+.. _Mypy: http://mypy-lang.org/
+
+
+Generic types
+-------------
+
+Psycopg `Connection` and `Cursor` objects are `~typing.Generic` objects and
+support a `!Row` parameter which is the type of the records returned.
+
+By default methods such as `Cursor.fetchall()` return normal tuples of unknown
+size and content. As such, the `connect()` function returns an object of type
+`!psycopg.Connection[Tuple[Any, ...]]` and `Connection.cursor()` returns an
+object of type `!psycopg.Cursor[Tuple[Any, ...]]`. If you are writing generic
+plumbing code it might be practical to use annotations such as
+`!Connection[Any]` and `!Cursor[Any]`.
+
+.. code:: python
+
+   conn = psycopg.connect() # type is psycopg.Connection[Tuple[Any, ...]]
+
+   cur = conn.cursor()      # type is psycopg.Cursor[Tuple[Any, ...]]
+
+   rec = cur.fetchone()     # type is Optional[Tuple[Any, ...]]
+
+   recs = cur.fetchall()    # type is List[Tuple[Any, ...]]
+
+
+.. _row-factory-static:
+
+Type of rows returned
+---------------------
+
+If you want to use connections and cursors returning your data as different
+types, for instance as dictionaries, you can use the `!row_factory` argument
+of the `~Connection.connect()` and the `~Connection.cursor()` method, which
+will control what type of record is returned by the fetch methods of the
+cursors and annotate the returned objects accordingly. See
+:ref:`row-factories` for more details.
+
+.. code:: python
+
+   dconn = psycopg.connect(row_factory=dict_row)
+   # dconn type is psycopg.Connection[Dict[str, Any]]
+
+   dcur = conn.cursor(row_factory=dict_row)
+   dcur = dconn.cursor()
+   # dcur type is psycopg.Cursor[Dict[str, Any]] in both cases
+
+   drec = dcur.fetchone()
+   # drec type is Optional[Dict[str, Any]]
+
+
+.. _example-pydantic:
+
+Example: returning records as Pydantic models
+---------------------------------------------
+
+Using Pydantic_ it is possible to enforce static typing at runtime. Using a
+Pydantic model factory the code can be checked statically using Mypy and
+querying the database will raise an exception if the rows returned is not
+compatible with the model.
+
+.. _Pydantic: https://pydantic-docs.helpmanual.io/
+
+The following example can be checked with ``mypy --strict`` without reporting
+any issue. Pydantic will also raise a runtime error in case the
+`!Person` is used with a query that returns incompatible data.
+
+.. code:: python
+
+    from datetime import date
+    from typing import Optional
+
+    import psycopg
+    from psycopg.rows import class_row
+    from pydantic import BaseModel
+
+    class Person(BaseModel):
+        id: int
+        first_name: str
+        last_name: str
+        dob: Optional[date]
+
+    def fetch_person(id: int) -> Person:
+        with psycopg.connect() as conn:
+            with conn.cursor(row_factory=class_row(Person)) as cur:
+                cur.execute(
+                    """
+                    SELECT id, first_name, last_name, dob
+                    FROM (VALUES
+                        (1, 'John', 'Doe', '2000-01-01'::date),
+                        (2, 'Jane', 'White', NULL)
+                    ) AS data (id, first_name, last_name, dob)
+                    WHERE id = %(id)s;
+                    """,
+                    {"id": id},
+                )
+                obj = cur.fetchone()
+
+                # reveal_type(obj) would return 'Optional[Person]' here
+
+                if not obj:
+                    raise KeyError(f"person {id} not found")
+
+                # reveal_type(obj) would return 'Person' here
+
+                return obj
+
+    for id in [1, 2]:
+        p = fetch_person(id)
+        if p.dob:
+            print(f"{p.first_name} was born in {p.dob.year}")
+        else:
+            print(f"Who knows when {p.first_name} was born")
index 18f27f033db1b4469dd36bf4d71a5d969c3b1bac..f0ae5c19b06c9098357ac0aaff4039d8a6221fe5 100644 (file)
@@ -204,8 +204,9 @@ What's new in Psycopg 3
 
 - :ref:`Asynchronous support <async>`
 - :ref:`Server-side parameters binding <server-side-binding>`
-- :ref:`prepared statements <prepared-statements>`
+- :ref:`Prepared statements <prepared-statements>`
 - :ref:`Binary communication <binary-data>`
 - :ref:`Python-based COPY support <copy>`
-- :ref:`Support for static typing <row-factory-static>`
+- :ref:`Support for static typing <static-typing>`
+- :ref:`A redesigned connection pool <connection-pools>`
 - :ref:`Direct access to the libpq functionalities <psycopg.pq>`
index f6c7d03890da45a7d61098c59ab3873bc431b130..f40d6decaea489308cf96cae0d287b55c54060e5 100644 (file)
@@ -14,7 +14,8 @@ more modern PostgreSQL and Python features, such as:
 - :ref:`prepared statements <prepared-statements>`
 - :ref:`binary communication <binary-data>`
 - :ref:`great COPY support <copy>`
-- :ref:`support for static typing <row-factory-static>`
+- :ref:`support for static typing <static-typing>`
+- :ref:`a redesigned connection pool <connection-pools>`
 - :ref:`direct access to the libpq functionalities <psycopg.pq>`
 
 .. _Python: https://www.python.org/