classification
Title: sqlite3.Row doesn't have useful repr
Type: enhancement Stage: resolved
Components: Library (Lib) Versions: Python 3.9
process
Status: closed Resolution: rejected
Dependencies: Superseder:
Assigned To: Nosy List: Catherine.Devlin, berker.peksag, erlendaasland, ghaering, rhettinger, serhiy.storchaka, terry.reedy, vlad
Priority: normal Keywords: patch, patch, patch, patch

Created on 2019-02-02 13:02 by vlad, last changed 2021-05-19 06:52 by berker.peksag. This issue is now closed.

Files
File name Uploaded Description Edit
patch.diff erlendaasland, 2021-04-08 08:55
patch.diff erlendaasland, 2021-04-08 10:00
Pull Requests
URL Status Linked Edit
PR 11820 closed python-dev, 2019-02-11 15:44
Messages (15)
msg334746 - (view) Author: Vlad Shcherbina (vlad) * Date: 2019-02-02 13:02
To reproduce, run the following program:

import sqlite3
conn = sqlite3.connect(':memory:')
conn.row_factory = sqlite3.Row
print(conn.execute("SELECT 'John' AS name, 42 AS salary").fetchone())

It prints '<sqlite3.Row object at 0xffffffffffffff>'.
It would be nice if it printed something like "sqlite3.Row(name='Smith', saraly=42)" instead.
It wouldn't satisfy the 'eval(repr(x)) == x' property, but it's still better than nothing.

If the maintainers agree this is useful, I'll implement.
msg335102 - (view) Author: Terry J. Reedy (terry.reedy) * (Python committer) Date: 2019-02-08 18:35
Enhancements only go into future versions.

This would have the same pluses and minuses as printing dicts in full.  I don't know the general feeling about expanding the classes printed out like this, or whether Gerhard is still active.  If you don't get responses within a few days, you might try posting to python-ideas list.
msg335189 - (view) Author: Raymond Hettinger (rhettinger) * (Python committer) Date: 2019-02-11 02:30
+1 from me.  We're already made regular expression match objects less opaque and that has been useful.  There's no need for a python-ideas discussion for this.

If a repr doesn't round-trip, we generally put it angle brackets (see PEP 8):

    >>> re.search(r'([a-z]+)(\d*)', 'alpha7')
    <re.Match object; span=(0, 6), match='alpha7'>

The Row object access style uses square brackets and has a keys() method.  That suggests a dict-like representation would be intuitive and match how Row objects are used:  ro['salary'] and ro.keys().

Putting those two ideas together we get:

    <sqlite3.Row object; {'name': 'John', 'salary': 42}>

Note the OP's suggestion for keyword argument style doesn't make sense for two reasons: 1) Row objects don't allow attribute access (i.e. ro.name is invalid) and 2) the field names are not required to be valid Python identifiers (i.e. ro['class'] is possible but ro.class is syntactically invalid because "class" is a keyword).
msg335540 - (view) Author: Berker Peksag (berker.peksag) * (Python committer) Date: 2019-02-14 16:33
While the proposed formats look nice for artificial inputs, the Row object can have more than two fields and the value of a field can be much longer than that. So, in practice the new repr can make the situation worse than the current repr.

I think wrapping the output of row.keys() (https://docs.python.org/3/library/sqlite3.html#sqlite3.Row.keys) with something like reprlib.recursive_repr() would work better for Row objects with several fields.
msg336291 - (view) Author: Vlad Shcherbina (vlad) * Date: 2019-02-22 10:21
There is no need to add explicit knowledge of reprlib to the Row object or vice versa.

Those who use reprlib to limit output size will have no problem. Reprlib already truncates arbitrary reprs at the string level: https://github.com/python/cpython/blob/22bfe637ca7728e9f74d4dc8bb7a15ee2a913815/Lib/reprlib.py#L144

Those who use builtin repr() have to be prepared for the possibility of unbounded repr anyway, because all collection-like objects in Python have unbounded __repr__.

It would be annoying if some collection-like objects decided to be clever and omit parts of their regular __repr__ because they feel it's too much for the user to handle.
msg337030 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2019-03-03 13:39
I have the same doubts as Berker. sqlite3.Row represents a data from a database. Databases are used in particularly for storing a large amount of data which can not be all loaded in RAM. Therefore sqlite3.Row can contain a data with a very long repr.

If you need to inspect the sqlite3.Row object, it is easy to convert it to a list. If you want to see keys together with values, it is not hard to make a list of key-value pairs or a dict: list(zip(row.keys(), row)) or dict(zip(row.keys(), row)).
msg350159 - (view) Author: Raymond Hettinger (rhettinger) * (Python committer) Date: 2019-08-22 05:50
If this goes forward, the maximum output size needs to be limited in some way (like what is done in reprlib).

Given Serhiy's doubts, I'm only +0 on this.  Feel free to close this if you like.  For the most part, I don't think people even look at Row objects.
msg350764 - (view) Author: Vlad Shcherbina (vlad) * Date: 2019-08-29 10:43
1. "This patch adds too many lines of code and not enough value."
If the maintainers judge it so, I have nothing to say. You have the responsibility to keep the codebase relatively simple.

2a. "Existing programs made with the assumption that repr(row) is short (albeit useless) will break when it becomes not so short."
I have no experience maintaining popular language implementations, so I defer to your judgement on what level of backward compatibility is warranted.

2b. "New users of new repr(row) will run into problems because its size is unbounded."
I think this objection is invalid, as I explained in the comment: https://bugs.python.org/issue35889#msg336291
msg390512 - (view) Author: Erlend E. Aasland (erlendaasland) * (Python triager) Date: 2021-04-08 08:55
AFAICS, this is fixed by the attached one-liner, based on Serhiy's suggestion (based on Raymond's suggestion).


$ python3.10 test.py
<sqlite3.Row object at 0x102b91330>
$ ./python.exe test.py  # patch applied
<sqlite3.Row object at 0x10c06b600 {'number': 1, 'string': 'two', 'blob': b'\x1f'}>
$ cat test.py
import sqlite3
cx = sqlite3.connect(":memory:")
cx.row_factory = sqlite3.Row
query = """
  select
    1 as number,
    'two' as string,
    x'1f' as blob
"""
for row in cx.execute(query):
    print(repr(row))


If Berker & Serhiy approves, I'll create a PR for it.
msg390517 - (view) Author: Erlend E. Aasland (erlendaasland) * (Python triager) Date: 2021-04-08 09:33
Even better, let's keep Row as it is and just add this as an example in the docs. If a verbose repr is needed, it's easy to override __repr__.
msg390521 - (view) Author: Erlend E. Aasland (erlendaasland) * (Python triager) Date: 2021-04-08 10:00
Suggesting to either close this or add an example to the docs. Up to Berker / Serhiy.
msg393832 - (view) Author: Catherine Devlin (Catherine.Devlin) * Date: 2021-05-17 21:19
What about a __repr__ that includes the primary key value(s) (for tables where that is defined)?  That would be useful and informative, and also generally short.
msg393900 - (view) Author: Erlend E. Aasland (erlendaasland) * (Python triager) Date: 2021-05-18 19:30
> What about a __repr__ that includes the primary key value(s) (for tables where that is defined)?

I’d rather have that as a Row method, or read-only property. It should be straight-forward to implement. SQLite provides an API for such purposes.
msg393901 - (view) Author: Erlend E. Aasland (erlendaasland) * (Python triager) Date: 2021-05-18 19:36
See:
- https://www.sqlite.org/c3ref/column_database_name.html
- https://www.sqlite.org/c3ref/table_column_metadata.html

Notice that the former is only available if SQLITE_ENABLE_COLUMN_METADATA was defined at compile time.
msg393925 - (view) Author: Berker Peksag (berker.peksag) * (Python committer) Date: 2021-05-19 06:52
Let's close it as rejected. Thank you everyone for your input.
History
Date User Action Args
2021-05-19 06:52:29berker.peksagsetstatus: open -> closed
messages: + msg393925

keywords: patch, patch, patch, patch
resolution: rejected
stage: patch review -> resolved
2021-05-18 19:36:27erlendaaslandsetkeywords: patch, patch, patch, patch

messages: + msg393901
2021-05-18 19:30:50erlendaaslandsetkeywords: patch, patch, patch, patch

messages: + msg393900
2021-05-17 21:19:15Catherine.Devlinsetnosy: + Catherine.Devlin
messages: + msg393832
2021-04-08 10:00:09erlendaaslandsetfiles: + patch.diff

messages: + msg390521
2021-04-08 09:33:10erlendaaslandsetmessages: + msg390517
2021-04-08 08:55:02erlendaaslandsetfiles: + patch.diff
nosy: + erlendaasland
messages: + msg390512

2019-08-29 10:43:23vladsetmessages: + msg350764
2019-08-22 05:50:48rhettingersetkeywords: patch, patch, patch, patch

messages: + msg350159
versions: + Python 3.9, - Python 3.8
2019-03-03 13:39:02serhiy.storchakasetkeywords: patch, patch, patch, patch
nosy: + serhiy.storchaka
messages: + msg337030

2019-02-22 10:21:37vladsetmessages: + msg336291
2019-02-14 16:34:16berker.peksagsetpull_requests: - pull_request11844
2019-02-14 16:34:12berker.peksagsetpull_requests: - pull_request11845
2019-02-14 16:34:07berker.peksagsetpull_requests: - pull_request11846
2019-02-14 16:33:48berker.peksagsetkeywords: patch, patch, patch, patch
nosy: + berker.peksag
messages: + msg335540

2019-02-11 15:44:41python-devsetkeywords: + patch
stage: patch review
pull_requests: + pull_request11846
2019-02-11 15:44:31python-devsetkeywords: + patch
stage: (no value)
pull_requests: + pull_request11847
2019-02-11 15:44:23python-devsetkeywords: + patch
stage: (no value)
pull_requests: + pull_request11845
2019-02-11 15:44:15python-devsetkeywords: + patch
stage: (no value)
pull_requests: + pull_request11844
2019-02-11 02:30:05rhettingersetnosy: + rhettinger
messages: + msg335189
2019-02-08 18:35:02terry.reedysetnosy: + terry.reedy, ghaering

messages: + msg335102
versions: - Python 2.7, Python 3.4, Python 3.5, Python 3.6, Python 3.7
2019-02-02 13:02:16vladcreate