Skip to content

Commit ab05c7b

Browse files
committed
Support pickling Record-s
Closes#451
1 parent 7df9812 commit ab05c7b

File tree

5 files changed

+135
-9
lines changed

5 files changed

+135
-9
lines changed

‎asyncpg/protocol/protocol.pyx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1024,4 +1024,4 @@ def _create_record(object mapping, tuple elems):
10241024
return rec
10251025

10261026

1027-
Record=<object>record.ApgRecord_InitTypes()
1027+
Record, RecordDescriptor =record.ApgRecord_InitTypes()

‎asyncpg/protocol/record/__init__.pxd‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ cimport cpython
1010

1111
cdef extern from"record/recordobj.h":
1212

13-
cpython.PyTypeObject *ApgRecord_InitTypes()exceptNULL
13+
tupleApgRecord_InitTypes()
1414

1515
int ApgRecord_CheckExact(object)
1616
object ApgRecord_New(type, object, int)

‎asyncpg/protocol/record/recordobj.c‎

Lines changed: 124 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ static PyObject * record_new_items_iter(PyObject *);
2020
staticApgRecordObject*free_list[ApgRecord_MAXSAVESIZE];
2121
staticintnumfree[ApgRecord_MAXSAVESIZE];
2222

23+
staticPyObject*record_reconstruct_obj;
24+
staticPyObject*record_desc_reconstruct_obj;
25+
26+
staticPyMethodDefrecord_desc_methods[];
27+
2328
staticsize_tMAX_RECORD_SIZE= (
2429
((size_t)PY_SSIZE_T_MAX-sizeof(ApgRecordObject) -sizeof(PyObject*))
2530
/ sizeof(PyObject*)
@@ -575,14 +580,14 @@ record_repr(ApgRecordObject *v)
575580

576581

577582
staticPyObject*
578-
record_values(PyObject*o, PyObject*args)
583+
record_values(PyObject*o, PyObject*Py_UNUSED(unused))
579584
{
580585
returnrecord_iter(o);
581586
}
582587

583588

584589
staticPyObject*
585-
record_keys(PyObject*o, PyObject*args)
590+
record_keys(PyObject*o, PyObject*Py_UNUSED(unused))
586591
{
587592
if (!ApgRecord_Check(o)){
588593
PyErr_BadInternalCall();
@@ -594,7 +599,7 @@ record_keys(PyObject *o, PyObject *args)
594599

595600

596601
staticPyObject*
597-
record_items(PyObject*o, PyObject*args)
602+
record_items(PyObject*o, PyObject*Py_UNUSED(unused))
598603
{
599604
if (!ApgRecord_Check(o)){
600605
PyErr_BadInternalCall();
@@ -657,12 +662,71 @@ static PyMappingMethods record_as_mapping ={
657662
0/* mp_ass_subscript */
658663
};
659664

665+
staticPyMethodDefrecord_methods[];
666+
667+
staticPyObject*
668+
record_reduce(ApgRecordObject*o, PyObject*Py_UNUSED(unused))
669+
{
670+
PyObject*value=PyTuple_New(2);
671+
if (value==NULL){
672+
returnNULL;
673+
}
674+
Py_ssize_tlen=Py_SIZE(o);
675+
PyObject*state=PyTuple_New(1+len);
676+
if (state==NULL){
677+
Py_DECREF(value);
678+
returnNULL;
679+
}
680+
PyTuple_SET_ITEM(value, 0, record_reconstruct_obj);
681+
Py_INCREF(record_reconstruct_obj);
682+
PyTuple_SET_ITEM(value, 1, state);
683+
PyTuple_SET_ITEM(state, 0, (PyObject*)o->desc);
684+
Py_INCREF(o->desc);
685+
for (Py_ssize_ti=0; i<len; i++){
686+
PyObject*item=ApgRecord_GET_ITEM(o, i);
687+
PyTuple_SET_ITEM(state, i+1, item);
688+
Py_INCREF(item);
689+
}
690+
returnvalue;
691+
}
692+
693+
staticPyObject*
694+
record_reconstruct(PyObject*Py_UNUSED(unused), PyObject*args)
695+
{
696+
if (!PyTuple_CheckExact(args)){
697+
returnNULL;
698+
}
699+
Py_ssize_tlen=PyTuple_GET_SIZE(args);
700+
if (len<2){
701+
returnNULL;
702+
}
703+
len--;
704+
ApgRecordDescObject*desc= (ApgRecordDescObject*)PyTuple_GET_ITEM(args, 0);
705+
if (!ApgRecordDesc_CheckExact(desc)){
706+
returnNULL;
707+
}
708+
if (PyObject_Length(desc->mapping) !=len){
709+
returnNULL;
710+
}
711+
PyObject*record=ApgRecord_New(&ApgRecord_Type, (PyObject*)desc, len);
712+
if (record==NULL){
713+
returnNULL;
714+
}
715+
for (Py_ssize_ti=0; i<len; i++){
716+
PyObject*item=PyTuple_GET_ITEM(args, i+1);
717+
ApgRecord_SET_ITEM(record, i, item);
718+
Py_INCREF(item);
719+
}
720+
returnrecord;
721+
}
660722

661723
staticPyMethodDefrecord_methods[] ={
662724
{"values", (PyCFunction)record_values, METH_NOARGS},
663725
{"keys", (PyCFunction)record_keys, METH_NOARGS},
664726
{"items", (PyCFunction)record_items, METH_NOARGS},
665727
{"get", (PyCFunction)record_get, METH_VARARGS},
728+
{"__reduce__", (PyCFunction)record_reduce, METH_NOARGS},
729+
{"__reconstruct__", (PyCFunction)record_reconstruct, METH_VARARGS | METH_STATIC},
666730
{NULL, NULL} /* sentinel */
667731
};
668732

@@ -942,7 +1006,7 @@ record_new_items_iter(PyObject *seq)
9421006
}
9431007

9441008

945-
PyTypeObject*
1009+
PyObject*
9461010
ApgRecord_InitTypes(void)
9471011
{
9481012
if (PyType_Ready(&ApgRecord_Type) <0){
@@ -961,7 +1025,22 @@ ApgRecord_InitTypes(void)
9611025
returnNULL;
9621026
}
9631027

964-
return&ApgRecord_Type;
1028+
record_reconstruct_obj=PyCFunction_New(
1029+
&record_methods[5], (PyObject*)&ApgRecord_Type
1030+
);
1031+
record_desc_reconstruct_obj=PyCFunction_New(
1032+
&record_desc_methods[1], (PyObject*)&ApgRecordDesc_Type
1033+
);
1034+
1035+
PyObject*types=PyTuple_New(2);
1036+
if (types==NULL){
1037+
returnNULL;
1038+
}
1039+
PyTuple_SET_ITEM(types, 0, (PyObject*)&ApgRecord_Type);
1040+
Py_INCREF(&ApgRecord_Type);
1041+
PyTuple_SET_ITEM(types, 1, (PyObject*)&ApgRecordDesc_Type);
1042+
Py_INCREF(&ApgRecordDesc_Type);
1043+
returntypes;
9651044
}
9661045

9671046

@@ -987,15 +1066,54 @@ record_desc_traverse(ApgRecordDescObject *o, visitproc visit, void *arg)
9871066
}
9881067

9891068

1069+
staticPyObject*record_desc_reduce(ApgRecordDescObject*o, PyObject*Py_UNUSED(unused))
1070+
{
1071+
PyObject*value=PyTuple_New(2);
1072+
if (value==NULL){
1073+
returnNULL;
1074+
}
1075+
PyObject*state=PyTuple_New(2);
1076+
if (state==NULL){
1077+
Py_DECREF(value);
1078+
returnNULL;
1079+
}
1080+
PyTuple_SET_ITEM(value, 0, record_desc_reconstruct_obj);
1081+
Py_INCREF(record_desc_reconstruct_obj);
1082+
PyTuple_SET_ITEM(value, 1, state);
1083+
PyTuple_SET_ITEM(state, 0, o->mapping);
1084+
Py_INCREF(o->mapping);
1085+
PyTuple_SET_ITEM(state, 1, o->keys);
1086+
Py_INCREF(o->keys);
1087+
returnvalue;
1088+
}
1089+
1090+
1091+
staticPyObject*record_desc_reconstruct(PyObject*Py_UNUSED(unused), PyObject*args)
1092+
{
1093+
if (PyTuple_GET_SIZE(args) !=2){
1094+
returnNULL;
1095+
}
1096+
returnApgRecordDesc_New(PyTuple_GET_ITEM(args, 0), PyTuple_GET_ITEM(args, 1));
1097+
}
1098+
1099+
1100+
staticPyMethodDefrecord_desc_methods[] ={
1101+
{"__reduce__", (PyCFunction)record_desc_reduce, METH_NOARGS},
1102+
{"__reconstruct__", (PyCFunction)record_desc_reconstruct, METH_VARARGS | METH_STATIC},
1103+
{NULL, NULL} /* sentinel */
1104+
};
1105+
1106+
9901107
PyTypeObjectApgRecordDesc_Type={
9911108
PyVarObject_HEAD_INIT(NULL, 0)
992-
.tp_name="RecordDescriptor",
1109+
.tp_name="asyncpg.protocol.protocol.RecordDescriptor",
9931110
.tp_basicsize=sizeof(ApgRecordDescObject),
9941111
.tp_dealloc= (destructor)record_desc_dealloc,
9951112
.tp_getattro=PyObject_GenericGetAttr,
9961113
.tp_flags=Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
9971114
.tp_traverse= (traverseproc)record_desc_traverse,
9981115
.tp_iter=PyObject_SelfIter,
1116+
.tp_methods=record_desc_methods,
9991117
};
10001118

10011119

‎asyncpg/protocol/record/recordobj.h‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ extern PyTypeObject ApgRecordDesc_Type;
4646
#defineApgRecord_GET_ITEM(op, i) \
4747
(((ApgRecordObject *)(op))->ob_item[i])
4848

49-
PyTypeObject*ApgRecord_InitTypes(void);
49+
PyObject*ApgRecord_InitTypes(void);
5050
PyObject*ApgRecord_New(PyTypeObject*, PyObject*, Py_ssize_t);
5151
PyObject*ApgRecordDesc_New(PyObject*, PyObject*);
5252

‎tests/test_record.py‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,3 +575,11 @@ class MyRecordBad:
575575
'record_class is expected to be a subclass of asyncpg.Record',
576576
):
577577
awaitself.connect(record_class=MyRecordBad)
578+
579+
deftest_record_pickle(self):
580+
r=pickle.loads(pickle.dumps(Record(R_AB, (42, 43))))
581+
self.assertEqual(len(r), 2)
582+
self.assertEqual(r[0], 42)
583+
self.assertEqual(r[1], 43)
584+
self.assertEqual(r['a'], 42)
585+
self.assertEqual(r['b'], 43)

0 commit comments

Comments
(0)