Skip to content

Commit d554827

Browse files
authored
Introduce new type for RESP3 PUSH notifications (#208)
* Introduce new type for RESP3 PUSH notifications Allow clients to distinguish between RESP3 arrays and PUSH types by introducing PushNotification type which subclasses list. Fix #128 * Use simpler solution to preallocate PushNotification list * Another attempt to make PushNotificationType compatible with PyPy
1 parent 58fe960 commit d554827

File tree

6 files changed

+76
-3
lines changed

6 files changed

+76
-3
lines changed

hiredis/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
from hiredis.hiredis import Reader, HiredisError, pack_command, ProtocolError, ReplyError
1+
from hiredis.hiredis import Reader, HiredisError, pack_command, ProtocolError, ReplyError, PushNotification
22
from hiredis.version import __version__
33

44
__all__ = [
55
"Reader",
66
"HiredisError",
77
"pack_command",
88
"ProtocolError",
9+
"PushNotification",
910
"ReplyError",
1011
"__version__"]

hiredis/hiredis.pyi

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ class ReplyError(HiredisError):
1313
...
1414

1515

16+
class PushNotification(list):
17+
...
18+
19+
1620
class Reader:
1721
def __init__(
1822
self,

src/hiredis.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@ PyMODINIT_FUNC PyInit_hiredis(void)
5959
return NULL;
6060
}
6161

62+
PushNotificationType.tp_base = &PyList_Type;
63+
if (PyType_Ready(&PushNotificationType) < 0) {
64+
return NULL;
65+
}
66+
6267
mod_hiredis = PyModule_Create(&hiredis_ModuleDef);
6368

6469
/* Setup custom exceptions */
@@ -79,5 +84,8 @@ PyMODINIT_FUNC PyInit_hiredis(void)
7984
Py_INCREF(&hiredis_ReaderType);
8085
PyModule_AddObject(mod_hiredis, "Reader", (PyObject *)&hiredis_ReaderType);
8186

87+
Py_INCREF(&PushNotificationType);
88+
PyModule_AddObject(mod_hiredis, "PushNotification", (PyObject *)&PushNotificationType);
89+
8290
return mod_hiredis;
8391
}

src/reader.c

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "reader.h"
22

33
#include <assert.h>
4+
#include <Python.h>
45

56
static void Reader_dealloc(hiredis_ReaderObject *self);
67
static int Reader_traverse(hiredis_ReaderObject *self, visitproc visit, void *arg);
@@ -14,6 +15,10 @@ static PyObject *Reader_len(hiredis_ReaderObject *self);
1415
static PyObject *Reader_has_data(hiredis_ReaderObject *self);
1516
static PyObject *Reader_set_encoding(hiredis_ReaderObject *self, PyObject *args, PyObject *kwds);
1617

18+
static int PushNotificationType_init(PushNotificationObject *self, PyObject *args, PyObject *kwds);
19+
/* Create a new instance of PushNotificationType with preallocated number of elements */
20+
static PyObject* PushNotificationType_New(Py_ssize_t size);
21+
1722
static PyMethodDef hiredis_ReaderMethods[] = {
1823
{"feed", (PyCFunction)Reader_feed, METH_VARARGS, NULL },
1924
{"gets", (PyCFunction)Reader_gets, METH_VARARGS, NULL },
@@ -66,6 +71,16 @@ PyTypeObject hiredis_ReaderType = {
6671
Reader_new, /*tp_new */
6772
};
6873

74+
PyTypeObject PushNotificationType = {
75+
PyVarObject_HEAD_INIT(NULL, 0)
76+
.tp_name = MOD_HIREDIS ".PushNotification",
77+
.tp_basicsize = sizeof(PushNotificationObject),
78+
.tp_itemsize = 0,
79+
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
80+
.tp_doc = "Redis PUSH notification type",
81+
.tp_init = (initproc) PushNotificationType_init,
82+
};
83+
6984
static void *tryParentize(const redisReadTask *task, PyObject *obj) {
7085
PyObject *parent;
7186
if (task && task->parent) {
@@ -165,6 +180,9 @@ static void *createArrayObject(const redisReadTask *task, size_t elements) {
165180
case REDIS_REPLY_MAP:
166181
obj = PyDict_New();
167182
break;
183+
case REDIS_REPLY_PUSH:
184+
obj = PushNotificationType_New(elements);
185+
break;
168186
default:
169187
obj = PyList_New(elements);
170188
}
@@ -199,6 +217,41 @@ static void freeObject(void *obj) {
199217
Py_XDECREF(obj);
200218
}
201219

220+
static int PushNotificationType_init(PushNotificationObject *self, PyObject *args, PyObject *kwds) {
221+
return PyList_Type.tp_init((PyObject *)self, args, kwds);
222+
}
223+
224+
static PyObject* PushNotificationType_New(Py_ssize_t size) {
225+
/* Check for negative size */
226+
if (size < 0) {
227+
PyErr_SetString(PyExc_SystemError, "negative list size");
228+
return NULL;
229+
}
230+
231+
/* Check for potential overflow */
232+
if ((size_t)size > PY_SSIZE_T_MAX / sizeof(PyObject*)) {
233+
return PyErr_NoMemory();
234+
}
235+
236+
#ifdef PYPY_VERSION
237+
PyObject* obj = PyObject_CallObject((PyObject *) &PushNotificationType, NULL);
238+
#else
239+
PyObject* obj = PyType_GenericNew(&PushNotificationType, NULL, NULL);
240+
#endif
241+
if (obj == NULL) {
242+
return NULL;
243+
}
244+
245+
int res = PyList_SetSlice(obj, PY_SSIZE_T_MAX, PY_SSIZE_T_MAX, PyList_New(size));
246+
247+
if (res == -1) {
248+
Py_DECREF(obj);
249+
return NULL;
250+
}
251+
252+
return obj;
253+
}
254+
202255
redisReplyObjectFunctions hiredis_ObjectFunctions = {
203256
createStringObject, // void *(*createString)(const redisReadTask*, char*, size_t);
204257
createArrayObject, // void *(*createArray)(const redisReadTask*, size_t);

src/reader.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,12 @@ typedef struct {
2323
} error;
2424
} hiredis_ReaderObject;
2525

26+
typedef struct {
27+
PyListObject list;
28+
} PushNotificationObject;
29+
2630
extern PyTypeObject hiredis_ReaderType;
31+
extern PyTypeObject PushNotificationType;
2732
extern redisReplyObjectFunctions hiredis_ObjectFunctions;
2833

2934
#endif

tests/test_reader.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,9 +185,11 @@ def test_dict_with_unhashable_key(reader):
185185
with pytest.raises(TypeError):
186186
reader.gets()
187187

188-
def test_vector(reader):
188+
def test_vector(reader):
189189
reader.feed(b">4\r\n+pubsub\r\n+message\r\n+channel\r\n+message\r\n")
190-
assert [b"pubsub", b"message", b"channel", b"message"] == reader.gets()
190+
result = reader.gets()
191+
assert isinstance(result, hiredis.PushNotification)
192+
assert [b"pubsub", b"message", b"channel", b"message"] == result
191193

192194
def test_verbatim_string(reader):
193195
value = b"text"

0 commit comments

Comments
 (0)