Skip to content

Commit

Permalink
Replace bool support with unicode support
Browse files Browse the repository at this point in the history
This is in line with python-memcached. Closes #242, #234.
  • Loading branch information
lericson committed Nov 9, 2018
1 parent cb45ab3 commit e269bbd
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 39 deletions.
40 changes: 23 additions & 17 deletions src/_pylibmcmodule.c
Expand Up @@ -650,16 +650,17 @@ static PyObject *_PylibMC_deserialize_native(PylibMC_Client *self, PyObject *val
break;
case PYLIBMC_FLAG_INTEGER:
case PYLIBMC_FLAG_LONG:
case PYLIBMC_FLAG_BOOL:
if (value) {
retval = PyLong_FromString(PyBytes_AS_STRING(value), NULL, 10);
} else {
retval = _PyLong_FromStringAndSize(value_str, value_size, NULL, 10);;
}
if (retval != NULL && dtype == PYLIBMC_FLAG_BOOL) {
PyObject *bool_retval = PyBool_FromLong(PyLong_AS_LONG(retval));
Py_DECREF(retval);
retval = bool_retval;
break;
case PYLIBMC_FLAG_TEXT:
if (value) {
retval = PyUnicode_FromEncodedObject(value, NULL, NULL);
} else {
retval = PyUnicode_FromStringAndSize(value_str, value_size);
}
break;
case PYLIBMC_FLAG_NONE:
Expand Down Expand Up @@ -724,7 +725,7 @@ static PyObject *PylibMC_Client_get(PylibMC_Client *self, PyObject *args) {

if (!_key_normalized_obj(&key)) {
return NULL;
} else if (!PySequence_Length(key) ) {
} else if (!PySequence_Length(key)) {
Py_INCREF(default_value);
return default_value;
}
Expand All @@ -737,18 +738,23 @@ static PyObject *PylibMC_Client_get(PylibMC_Client *self, PyObject *args) {

Py_DECREF(key);

if (mc_val != NULL) {
if (error == MEMCACHED_SUCCESS) {
/* note that mc_val can and is NULL for zero-length values. */
PyObject *r = _PylibMC_parse_memcached_value(self, mc_val, val_size, flags);
free(mc_val);

if (mc_val != NULL) {
free(mc_val);
}

if (_PylibMC_cache_miss_simulated(r)) {
Py_INCREF(default_value);
return default_value;
}

return r;
} else if (error == MEMCACHED_SUCCESS) {
/* This happens for empty values, and so we fake an empty string. */
return PyBytes_FromStringAndSize("", 0);
} else if (error == MEMCACHED_NOTFOUND) {
}

if (error == MEMCACHED_NOTFOUND) {
Py_INCREF(default_value);
return default_value;
}
Expand Down Expand Up @@ -1248,12 +1254,12 @@ static int _PylibMC_serialize_native(PylibMC_Client *self, PyObject *value_obj,
/* Make store_val an owned reference */
store_val = value_obj;
Py_INCREF(store_val);
} else if (PyUnicode_Check(value_obj)) {
store_flags = PYLIBMC_FLAG_TEXT;
store_val = PyUnicode_AsUTF8String(value_obj);
} else if (PyBool_Check(value_obj)) {
store_flags |= PYLIBMC_FLAG_BOOL;
/* bool cannot be subclassed; there are only two singleton values,
Py_True and Py_False */
const char *value_str = (value_obj == Py_True) ? "1" : "0";
store_val = PyBytes_FromString(value_str);
store_flags |= PYLIBMC_FLAG_INTEGER;
store_val = PyBytes_FromStringAndSize(&"01"[value_obj == Py_True], 1);
#if PY_MAJOR_VERSION >= 3
} else if (PyLong_Check(value_obj)) {
store_flags |= PYLIBMC_FLAG_LONG;
Expand Down
7 changes: 3 additions & 4 deletions src/_pylibmcmodule.h
Expand Up @@ -65,13 +65,12 @@ enum PylibMC_Flags {
PYLIBMC_FLAG_PICKLE = (1 << 0),
PYLIBMC_FLAG_INTEGER = (1 << 1),
PYLIBMC_FLAG_LONG = (1 << 2),
/* Note: python-memcached doesn't handle bools, this flag is pylibmc exclusive. */
PYLIBMC_FLAG_BOOL = (1 << 4),
PYLIBMC_FLAG_ZLIB = (1 << 3)
PYLIBMC_FLAG_ZLIB = (1 << 3),
PYLIBMC_FLAG_TEXT = (1 << 4),
};

#define PYLIBMC_FLAG_TYPES (PYLIBMC_FLAG_PICKLE | PYLIBMC_FLAG_INTEGER | \
PYLIBMC_FLAG_LONG | PYLIBMC_FLAG_BOOL)
PYLIBMC_FLAG_LONG | PYLIBMC_FLAG_TEXT)
/* }}} */

/* Behaviors that only affects pylibmc (i.e. not memached_set_behavior etc) */
Expand Down
30 changes: 25 additions & 5 deletions tests/doctests.txt
Expand Up @@ -29,10 +29,30 @@ string containing "hello" by decoding a byte string
'bar'

We should handle empty values just nicely.
>>> c.set("foo", "")
>>> c.set("foo", u"")
True
>>> c.get("foo")
''
>>> c.set("foo", b"")
True
>>> c.get("foo")
b''

Some sort of idempotency seems meaningful
>>> c.set("foo", "bar")
True
>>> c.get("foo")
'bar'

Important that we know how to deal with bytes and unicode data
>>> c.set("foo", b"bar")
True
>>> isinstance(c.get("foo"), bytes)
True
>>> c.set("foo", u"bar")
True
>>> isinstance(c.get("foo"), bytes)
False

Now this section is because most other implementations ignore zero-keys.
>>> c.get("")
Expand Down Expand Up @@ -153,12 +173,12 @@ True
Get and set booleans.
>>> c.set("greta", True)
True
>>> c.get("greta")
True
>>> int(c.get("greta"))
1
>>> c.set("greta", False)
True
>>> c.get("greta")
False
>>> int(c.get("greta"))
0
>>> c.delete("greta")
True

Expand Down
32 changes: 19 additions & 13 deletions tests/test_serialization.py
Expand Up @@ -25,36 +25,42 @@ def long_(val):
# this happens under Python 3
return val

f_none = 0
f_pickle, f_int, f_long, f_zlib, f_text = (1 << i for i in range(5))

class SerializationMethodTests(PylibmcTestCase):
"""Coverage tests for serialize and deserialize."""

def test_integers(self):
c = make_test_client(binary=True)
if sys.version_info[0] == 3:
eq_(c.serialize(1), (b'1', 4))
eq_(c.serialize(2**64), (b'18446744073709551616', 4))
eq_(c.serialize(1), (b'1', f_long))
eq_(c.serialize(2**64), (b'18446744073709551616', f_long))
else:
eq_(c.serialize(1), (b'1', 2))
eq_(c.serialize(2**64), (b'18446744073709551616', 4))
eq_(c.serialize(1), (b'1', f_int))
eq_(c.serialize(2**64), (b'18446744073709551616', f_long))

eq_(c.deserialize(b'1', 2), 1)
eq_(c.deserialize(b'1', f_int), 1)

eq_(c.deserialize(b'18446744073709551616', 4), 2**64)
eq_(c.deserialize(b'1', 4), long_(1))
eq_(c.deserialize(b'18446744073709551616', f_long), 2**64)
eq_(c.deserialize(b'1', f_long), long_(1))

def test_nonintegers(self):
# tuples (python_value, (expected_bytestring, expected_flags))
SERIALIZATION_TEST_VALUES = [
# booleans
(True, (b'1', 16)),
(False, (b'0', 16)),
# booleans are just ints
(True, (b'1', f_int)),
(False, (b'0', f_int)),
# bytestrings
(b'asdf', (b'asdf', 0)),
(b'\xb5\xb1\xbf\xed\xa9\xc2{8', (b'\xb5\xb1\xbf\xed\xa9\xc2{8', 0)),
(b'asdf', (b'asdf', f_none)),
(b'\xb5\xb1\xbf\xed\xa9\xc2{8', (b'\xb5\xb1\xbf\xed\xa9\xc2{8', f_none)),
(b'', (b'', f_none)),
# unicode objects
(u'åäö', (u'åäö'.encode('utf-8'), f_text)),
(u'', (b'', f_text)),
# objects
(datetime.date(2015, 12, 28), (pickle.dumps(datetime.date(2015, 12, 28),
protocol=-1), 1)),
protocol=-1), f_pickle)),
]

c = make_test_client(binary=True)
Expand Down

0 comments on commit e269bbd

Please sign in to comment.