-
Notifications
You must be signed in to change notification settings - Fork 22
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Provide a "class" row factory #42
Comments
One way to design the "class" row factory would be to have a the
So this will try to fit in to the existing code structure that is present in the
|
Here's the PR for the addition |
Is this the way that psycopg3 does it, with all columns names being passed to the class constructor as keyword arguments? |
The column names aren't passed as a class constructor but the pointer to the particular class is passed to the constructor. To make it behave like a function (as it is shown in the existing format of comdb) I used the specified format in the
I adopted a similar method and to make it easy to understand and read I put it in a class |
the |
Right, I understand that, I was asking about how that class is called. Does psycopg3 turn every column name into a keyword argument for that class constructor? |
In its source code it does get the column names using another function and zips it to keyword argument of values. |
Its easy to convert a dictionary to a class object. If there is a blueprint for getting the dictionary within the |
My concerns about a class row factory have never been about ease of implementation, they're about performance and about robustness. On the performance side, this approach to a class row factory means that for each row a On the robustness side, I'm wondering how useful it is to assume that column names will exactly match the parameter names of the If psycopg3 has done this the same way - with a dict being created for each row and used to provide keyword arguments to the class constructor based on the column names - that helps to set my mind at ease about both the performance and robustness aspect. That's why I'm inquiring about the approach they took. |
The column names and values are returned as a dictionary
These will match with the parameters of the class specified if the parameter name and the key names (in this case the column name) will be the same so the class should look something like
Just how it has been described in the original psycopg implementation |
Also important thing. Datatype of the attributes of the dataclass should be the same as the datatype of the corresponding values of the dictionary. |
I'm pretty sure I don't want to expose this feature, at least as things stand today. There's a few different things that make this make more sense for psycopg3 than it makes for Comdb2, but the biggest one is that I'm reluctant to add more code that makes static type checking difficult. The psycopg3 Connection and Cursor classes are generic, so a type checker like mypy or pylance can catch a bug like this: from dataclasses import dataclass
@dataclass
class Point:
x: int
y: int
conn = psycopg.connect(row_factory=class_row(Point))
cur = conn.cursor()
cur.execute("select 1, 2")
for point in cur.fetchall():
print(f"x={point.x} y={point.y} z={point.z}") The type checker knows that Compare that against this example that uses from dataclasses import dataclass
from comdb2.dbapi2 import connect
from comdb2.factories import ClassRowFactory
@dataclass
class Point:
x: int
y: int
conn = connect("mattdb")
conn.row_factory = ClassRowFactory(Point)
cur = conn.cursor()
cur.execute("select 1, 2")
for point in cur.fetchall():
print(f"x={point.x} y={point.y} z={point.z}") In the case of It seems a bit unfair to dislike this because of an unrelated feature that from dataclasses import dataclass
from comdb2.dbapi2 import connect
from comdb2.factories import dict_row_factory
@dataclass
class Point:
x: int
y: int
conn = connect("mattdb")
conn.row_factory = dict_row_factory
cur = conn.cursor()
cur.execute("select 1, 2")
for row in cur.fetchall():
point = Point(**row)
print(f"x={point.x} y={point.y} z={point.z}") then the type checker would catch the bug. That's only one more line of code, and it makes it so that type checkers can usefully check the processing of each row. If someone wants to convert each row to a class, I'd rather they do it this way so that the type checker understands what's happening. That's not a "no" for doing this proposed change, but I think it is a "not yet". I don't think this change makes sense unless and until our |
And sorry for taking so long to reply here. I was hoping I'd find time to try making our Connection and Cursor classes generic, and never managed to get around to it. |
Is your feature request related to a problem? Please describe.
Currently, comdb2 module provides dict and namedtuple factories. It would also be nice to provide a "class" row factory which would allow rows returned as client-defined dataclasses, pydantic models, etc.
Describe the solution you'd like
See a similar concept in psycopg's
class_row
factory: https://www.psycopg.org/psycopg3/docs/api/rows.html#psycopg.rows.class_rowThis also allows nice static type checking: https://www.psycopg.org/psycopg3/docs/advanced/typing.html#example-returning-records-as-pydantic-models
The text was updated successfully, but these errors were encountered: