Skip to content

Commit 5f2355e

Browse files
committed
Added row_factory method for query results
Signed-off-by: chandr-andr (Kiselev Aleksandr) <chandr@chandr.net>
1 parent 7e7a228 commit 5f2355e

File tree

3 files changed

+103
-7
lines changed

3 files changed

+103
-7
lines changed

python/psqlpy/_internal/__init__.pyi

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ from typing_extensions import Self
88
_CustomClass = TypeVar(
99
"_CustomClass",
1010
)
11+
_RowFactoryRV = TypeVar(
12+
"_RowFactoryRV",
13+
)
1114

1215
class QueryResult:
1316
"""Result."""
@@ -16,7 +19,11 @@ class QueryResult:
1619
self: Self,
1720
custom_decoders: dict[str, Callable[[bytes], Any]] | None = None,
1821
) -> list[dict[Any, Any]]:
19-
"""Return result from database as a list of dicts."""
22+
"""Return result from database as a list of dicts.
23+
24+
`custom_decoders` must be used when you use
25+
PostgreSQL Type which isn't supported, read more in our docs.
26+
"""
2027
def as_class(
2128
self: Self,
2229
as_class: Callable[..., _CustomClass],
@@ -52,12 +59,39 @@ class QueryResult:
5259
)
5360
```
5461
"""
62+
def row_factory(
63+
self,
64+
row_factory: Callable[[dict[str, Any]], _RowFactoryRV],
65+
custom_decoders: dict[str, Callable[[bytes], Any]] | None = None,
66+
) -> list[_RowFactoryRV]:
67+
"""Use custom function to convert results from database.
68+
69+
`custom_decoders` must be used when you use
70+
PostgreSQL Type isn't supported, read more in the docs.
71+
72+
Argument order: firstly we apply `custom_decoders` (if specified),
73+
then we apply `row_factory`.
74+
75+
### Parameters:
76+
- `row_factory`: function which takes `dict[str, Any]` as an argument.
77+
- `custom_decoders`: functions for custom decoding.
78+
79+
### Returns:
80+
List of type that return passed `row_factory`.
81+
"""
5582

5683
class SingleQueryResult:
5784
"""Single result."""
5885

59-
def result(self: Self) -> dict[Any, Any]:
60-
"""Return result from database as a dict."""
86+
def result(
87+
self: Self,
88+
custom_decoders: dict[str, Callable[[bytes], Any]] | None = None,
89+
) -> dict[Any, Any]:
90+
"""Return result from database as a dict.
91+
92+
`custom_decoders` must be used when you use
93+
PostgreSQL Type which isn't supported, read more in our docs.
94+
"""
6195
def as_class(
6296
self: Self,
6397
as_class: Callable[..., _CustomClass],
@@ -96,6 +130,26 @@ class SingleQueryResult:
96130
)
97131
```
98132
"""
133+
def row_factory(
134+
self,
135+
row_factory: Callable[[dict[str, Any]], _RowFactoryRV],
136+
custom_decoders: dict[str, Callable[[bytes], Any]] | None = None,
137+
) -> _RowFactoryRV:
138+
"""Use custom function to convert results from database.
139+
140+
`custom_decoders` must be used when you use
141+
PostgreSQL Type isn't supported, read more in our docs.
142+
143+
Argument order: firstly we apply `custom_decoders` (if specified),
144+
then we apply `row_factory`.
145+
146+
### Parameters:
147+
- `row_factory`: function which takes `list[dict[str, Any]]` as an argument.
148+
- `custom_decoders`: functions for custom decoding.
149+
150+
### Returns:
151+
Type that return passed function.
152+
"""
99153

100154
class IsolationLevel(Enum):
101155
"""Class for Isolation Level for transactions."""

src/query_result.rs

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,28 @@ impl PSQLDriverPyQueryResult {
8989

9090
Ok(res.to_object(py))
9191
}
92+
93+
/// Convert result from database with function passed from Python.
94+
///
95+
/// # Errors
96+
///
97+
/// May return Err Result if can not convert
98+
/// postgres type with custom function.
99+
#[allow(clippy::needless_pass_by_value)]
100+
pub fn row_factory<'a>(
101+
&'a self,
102+
py: Python<'a>,
103+
row_factory: Py<PyAny>,
104+
custom_decoders: Option<Py<PyDict>>,
105+
) -> RustPSQLDriverPyResult<Py<PyAny>> {
106+
let mut res: Vec<Py<PyAny>> = vec![];
107+
for row in &self.inner {
108+
let pydict: pyo3::Bound<'_, PyDict> = row_to_dict(py, row, &custom_decoders)?;
109+
let row_factory_class = row_factory.call_bound(py, (pydict,), None)?;
110+
res.push(row_factory_class);
111+
}
112+
Ok(res.to_object(py))
113+
}
92114
}
93115

94116
#[pyclass(name = "SingleQueryResult")]
@@ -121,8 +143,13 @@ impl PSQLDriverSinglePyQueryResult {
121143
/// May return Err Result if can not convert
122144
/// postgres type to python, can not set new key-value pair
123145
/// in python dict or there are no result.
124-
pub fn result(&self, py: Python<'_>) -> RustPSQLDriverPyResult<Py<PyAny>> {
125-
Ok(row_to_dict(py, &self.inner, &None)?.to_object(py))
146+
#[allow(clippy::needless_pass_by_value)]
147+
pub fn result(
148+
&self,
149+
py: Python<'_>,
150+
custom_decoders: Option<Py<PyDict>>,
151+
) -> RustPSQLDriverPyResult<Py<PyAny>> {
152+
Ok(row_to_dict(py, &self.inner, &custom_decoders)?.to_object(py))
126153
}
127154

128155
/// Convert result from database to any class passed from Python.
@@ -141,4 +168,21 @@ impl PSQLDriverSinglePyQueryResult {
141168
let pydict: pyo3::Bound<'_, PyDict> = row_to_dict(py, &self.inner, &None)?;
142169
Ok(as_class.call_bound(py, (), Some(&pydict))?)
143170
}
171+
172+
/// Convert result from database with function passed from Python.
173+
///
174+
/// # Errors
175+
///
176+
/// May return Err Result if can not convert
177+
/// postgres type with custom function
178+
#[allow(clippy::needless_pass_by_value)]
179+
pub fn row_factory<'a>(
180+
&'a self,
181+
py: Python<'a>,
182+
row_factory: Py<PyAny>,
183+
custom_decoders: Option<Py<PyDict>>,
184+
) -> RustPSQLDriverPyResult<Py<PyAny>> {
185+
let pydict = row_to_dict(py, &self.inner, &custom_decoders)?.to_object(py);
186+
Ok(row_factory.call_bound(py, (pydict,), None)?)
187+
}
144188
}

src/value_converter.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,6 @@ pub fn convert_parameters(parameters: Py<PyAny>) -> RustPSQLDriverPyResult<Vec<P
325325
/// or value of the type is incorrect.
326326
#[allow(clippy::too_many_lines)]
327327
pub fn py_to_rust(parameter: &pyo3::Bound<'_, PyAny>) -> RustPSQLDriverPyResult<PythonDTO> {
328-
println!("{}", parameter.get_type().name()?);
329328
if parameter.is_none() {
330329
return Ok(PythonDTO::PyNone);
331330
}
@@ -490,7 +489,6 @@ pub fn py_to_rust(parameter: &pyo3::Bound<'_, PyAny>) -> RustPSQLDriverPyResult<
490489
}
491490

492491
if parameter.get_type().name()? == "decimal.Decimal" {
493-
println!("{}", parameter.str()?.extract::<&str>()?);
494492
return Ok(PythonDTO::PyDecimal(Decimal::from_str_exact(
495493
parameter.str()?.extract::<&str>()?,
496494
)?));

0 commit comments

Comments
 (0)