Skip to content

Commit 6efd75b

Browse files
committed
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
1 parent ac31d58 commit 6efd75b

File tree

6 files changed

+90
-3
lines changed

6 files changed

+90
-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: 67 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,55 @@ 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+
/* Create a new instance of PushNotificationType with preallocated number of elements */
225+
static PyObject* PushNotificationType_New(Py_ssize_t size) {
226+
/* Check for negative size */
227+
if (size < 0) {
228+
PyErr_SetString(PyExc_SystemError, "negative list size");
229+
return NULL;
230+
}
231+
232+
/* Check for potential overflow */
233+
if ((size_t)size > PY_SSIZE_T_MAX / sizeof(PyObject*)) {
234+
return PyErr_NoMemory();
235+
}
236+
237+
/* Create a new instance of PushNotificationType */
238+
PyObject* obj = PyType_GenericNew(&PushNotificationType, NULL, NULL);
239+
if (obj == NULL) {
240+
return NULL;
241+
}
242+
243+
/* Cast to PyListObject to access its fields */
244+
PyListObject* op = (PyListObject*)obj;
245+
246+
/* Allocate memory for the list items if size > 0 */
247+
if (size > 0) {
248+
size_t nbytes = (size_t)size * sizeof(PyObject*);
249+
op->ob_item = (PyObject**)PyMem_Malloc(nbytes);
250+
if (op->ob_item == NULL) {
251+
Py_DECREF(obj);
252+
return PyErr_NoMemory();
253+
}
254+
/* Initialize memory to zeros */
255+
memset(op->ob_item, 0, nbytes);
256+
}
257+
258+
/* Set the size and allocated fields */
259+
#if PY_VERSION_HEX >= 0x03090000
260+
Py_SET_SIZE(op, size);
261+
#else
262+
Py_SIZE(op) = size;
263+
#endif
264+
op->allocated = size;
265+
266+
return obj;
267+
}
268+
202269
redisReplyObjectFunctions hiredis_ObjectFunctions = {
203270
createStringObject, // void *(*createString)(const redisReadTask*, char*, size_t);
204271
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)