From: Daniele Varrazzo Date: Wed, 2 Mar 2022 00:48:30 +0000 (+0000) Subject: fix: don't raise error accessing Cursor.description after COPY_OUT X-Git-Tag: 3.1~175 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=59a1c99aff818a37ba5e53b4adad28670a757bdf;p=thirdparty%2Fpsycopg.git fix: don't raise error accessing Cursor.description after COPY_OUT COPY_OUT result advertises the number of columns but not their names (or types). Use a surrogate name for description (which is more useful than returning `None`, because at lest it tells how many columns were emitted). Close #235. --- diff --git a/docs/news.rst b/docs/news.rst index f96a54752..e03f04cfe 100644 --- a/docs/news.rst +++ b/docs/news.rst @@ -26,6 +26,8 @@ Psycopg 3.0.10 (unreleased) - Leave the connection working after interrupting a query with Ctrl-C (currently only for sync connections, :ticket:`#231`). +- Fix `Cursor.description` after a COPY ... TO STDOUT operation + (:ticket:`#235`). Current release diff --git a/psycopg/psycopg/_column.py b/psycopg/psycopg/_column.py index ab30b4d4f..9e4e7357c 100644 --- a/psycopg/psycopg/_column.py +++ b/psycopg/psycopg/_column.py @@ -7,8 +7,6 @@ The Column object in Cursor.description from typing import Any, NamedTuple, Optional, Sequence, TYPE_CHECKING from operator import attrgetter -from . import errors as e - if TYPE_CHECKING: from .cursor import BaseCursor @@ -28,10 +26,11 @@ class Column(Sequence[Any]): assert res fname = res.fname(index) - if not fname: - raise e.InterfaceError(f"no name available for column {index}") - - self._name = fname.decode(cursor._encoding) + if fname: + self._name = fname.decode(cursor._encoding) + else: + # COPY_OUT results have columns but no name + self._name = f"column_{index + 1}" self._data = ColumnData( ftype=res.ftype(index), diff --git a/tests/test_copy.py b/tests/test_copy.py index af541a256..4cea6646a 100644 --- a/tests/test_copy.py +++ b/tests/test_copy.py @@ -533,6 +533,19 @@ def test_str(conn): assert "[INTRANS]" in str(copy) +def test_description(conn): + with conn.cursor() as cur: + with cur.copy("copy (select 'This', 'Is', 'Text') to stdout") as copy: + len(cur.description) == 3 + assert cur.description[0].name == "column_1" + assert cur.description[2].name == "column_3" + list(copy.rows()) + + len(cur.description) == 3 + assert cur.description[0].name == "column_1" + assert cur.description[2].name == "column_3" + + @pytest.mark.parametrize( "format, buffer", [(Format.TEXT, "sample_text"), (Format.BINARY, "sample_binary")], diff --git a/tests/test_copy_async.py b/tests/test_copy_async.py index a5cfd605e..ba025e1a0 100644 --- a/tests/test_copy_async.py +++ b/tests/test_copy_async.py @@ -532,6 +532,19 @@ async def test_str(aconn): assert "[INTRANS]" in str(copy) +async def test_description(aconn): + async with aconn.cursor() as cur: + async with cur.copy("copy (select 'This', 'Is', 'Text') to stdout") as copy: + len(cur.description) == 3 + assert cur.description[0].name == "column_1" + assert cur.description[2].name == "column_3" + await alist(copy.rows()) + + len(cur.description) == 3 + assert cur.description[0].name == "column_1" + assert cur.description[2].name == "column_3" + + @pytest.mark.parametrize( "format, buffer", [(Format.TEXT, "sample_text"), (Format.BINARY, "sample_binary")],