]> git.ipfire.org Git - thirdparty/psycopg.git/commitdiff
docs: add literal strings examples
authorDaniele Varrazzo <daniele.varrazzo@gmail.com>
Sun, 3 Jul 2022 02:49:15 +0000 (03:49 +0100)
committerDaniele Varrazzo <daniele.varrazzo@gmail.com>
Sun, 3 Jul 2022 02:52:59 +0000 (03:52 +0100)
Move the PEP 675 section to the typing page.

docs/advanced/typing.rst
docs/basic/params.rst

index 9b58372a8040705a0d8ad532f96f709631ca4b63..df6483b7126a0be306a731237cf8679826121b69 100644 (file)
@@ -126,3 +126,55 @@ any issue. Pydantic will also raise a runtime error in case the
             print(f"{p.first_name} was born in {p.dob.year}")
         else:
             print(f"Who knows when {p.first_name} was born")
+
+
+.. _literal-string:
+
+Checking literal strings in queries
+-----------------------------------
+
+The `~Cursor.execute()` method and similar should only receive a literal
+string as input, according to :pep:`675`. This mean that the query should
+come from a literal string in your code, not from an arbitrary string
+expression.
+
+For instance, passing an argument to the query should be done via the second
+argument to `!execute()`, not by string composition:
+
+.. code:: python
+
+    def get_record(conn: psycopg.Connection[Any], id: int) -> Any:
+        cur = conn.execute("SELECT * FROM my_table WHERE id = %s" % id)  # BAD!
+        return cur.fetchone()
+
+    # the function should be implemented as:
+
+    def get_record(conn: psycopg.Connection[Any], id: int) -> Any:
+        cur = conn.execute("select * FROM my_table WHERE id = %s", (id,))
+        return cur.fetchone()
+
+If you are composing a query dynamically you should use the `sql.SQL` object
+and similar to escape safely table and field names. The parameter of the
+`!SQL()` object should be a literal string:
+
+.. code:: python
+
+    def count_records(conn: psycopg.Connection[Any], table: str) -> int:
+        query = "SELECT count(*) FROM %s" % table  # BAD!
+        return conn.execute(query).fetchone()[0]
+
+    # the function should be implemented as:
+
+    def count_records(conn: psycopg.Connection[Any], table: str) -> int:
+        query = sql.SQL("SELECT count(*) FROM {}").format(sql.Identifier(table))
+        return conn.execute(query).fetchone()[0]
+
+At the time of writing, no Python static analyzer implements this check (`mypy
+doesn't implement it`__, Pyre_ does, but `doesn't work with psycopg yet`__).
+Once the type checkers support will be complete, the above bad statements
+should be reported as errors.
+
+.. __: https://github.com/python/mypy/issues/12554
+.. __: https://github.com/facebook/pyre-check/issues/636
+
+.. _Pyre: https://pyre-check.org/
index 27d80431013dc51b44a5d728058caa38553b1216..15b876543dcb75a3adf195913726f4df45842597 100644 (file)
@@ -179,6 +179,11 @@ argument of the `Cursor.execute()` method::
     data = ("O'Reilly", )
     cur.execute(SQL, data)  # Note: no % operator
 
+.. note::
+
+    Python static code checkers are not quite there yet, but, in the future,
+    it will be possible to check your code for improper use of string
+    expressions in queries. See :ref:`literal-string` for details.
 
 .. seealso::
 
@@ -186,21 +191,6 @@ argument of the `Cursor.execute()` method::
     at :ref:`how Psycopg converts data types <types-adaptation>`.
 
 
-Using `LiteralString` in queries
---------------------------------
-
-The `~Cursor.execute()` method and similar should only receive a literal
-string as input, according to :pep:`675`.
-
-If you need to compose a query dynamically you should use `sql.SQL` and
-similar methods.
-
-At the time of writing the feature is experimental and only checked by the
-Pyre_ checker.
-
-.. _Pyre: https://pyre-check.org/
-
-
 .. index::
     pair: Binary; Parameters
 
@@ -248,5 +238,5 @@ requesting binary results is a clear winner is when you have large binary data
 in the database, such as images::
 
     cur.execute(
-        "select image_data from images where id = %s", [image_id], binary=True)
+        "SELECT image_data FROM images WHERE id = %s", [image_id], binary=True)
     data = cur.fetchone()[0]