Compare commits

..

No commits in common. "master" and "0.7.3" have entirely different histories.

17 changed files with 559 additions and 2489 deletions

7
.gitignore vendored
View File

@ -1,8 +1,5 @@
*.pyc *.pyc
*.pyo *.pyo
.DS_Store build/*
build dist/*
dist
tmp
MANIFEST MANIFEST
sendapatch.se

View File

@ -1,4 +1,4 @@
include README.rst LICENSE MANIFEST MANIFEST.in pooling.rst include README.rst LICENSE MANIFEST MANIFEST.in
include _pylibmcmodule.c _pylibmcmodule.h pylibmc-version.h include _pylibmcmodule.c _pylibmcmodule.h
include pylibmc.py include pylibmc.py
include setup.py tests.py include setup.py tests.py

View File

@ -1,126 +1,97 @@
`pylibmc` is a quick and small Python client for memcached__ written in C. `pylibmc` is a Python wrapper around the accompanying C Python extension
`_pylibmc`, which is a wrapper around `libmemcached` from TangentOrg.
__ http://memcached.org/ You have to install `libmemcached` separately, and have your compiler and
linker find the include files and libraries.
It builds on the famous `libmemcached`__ C client from TangentOrg__, notable for With `libmemcached` installed and this package set up, the following basic
its speed and flexibility. usage example should work::
__ http://tangent.org/552/libmemcached.html >>> import pylibmc
__ http://tangent.org/ >>> mc = pylibmc.Client(["127.0.0.1:11211"])
>>> mc.set("foo", "Hello world!")
True
>>> mc.get("foo")
'Hello world!'
`libmemcached` must be installed separately, and be available to the compiler The API is pretty much `python-memcached`. Some parts of `libmemcached` aren't
and linker. exposed yet. I think.
For installation instructions, usage notes and reference documentation, see Behaviors
pylibmc__'s home at http://sendapatch.se/projects/pylibmc/. =========
`libmemcached` has ways of telling it how to behave. You'll have to refer to
its documentation on what the different behaviors do.
To change behaviors, quite simply::
>>> mc.behaviors["hash"] = "fnv1a_32"
For a list of the defined behavior key names, see what the keys of a client is.
For example::
>>> mc.behaviors.keys() # doctest: +NORMALIZE_WHITESPACE
['hash', 'connect timeout', 'cache lookups', 'buffer requests',
'verify key', 'support cas', 'poll timeout', 'no block', 'tcp nodelay',
'distribution', 'sort hosts']
The ``hash`` and ``distribution`` keys are mapped by the Python module to constant
integer values used by `libmemcached`. See ``pylibmc.hashers`` and
``pylibmc.distributions``.
__ http://sendapatch.se/projects/pylibmc/
Comparison to other libraries Comparison to other libraries
============================= =============================
Why use `pylibmc`? Because it's fast. Why use `pylibmc`? Because it's fast.
`See this (a bit old) speed comparison`__, or `amix.dk's comparison`__. `See this (a bit old) speed comparison <http://lericson.blogg.se/code/2008/november/pylibmc-051.html>`_.
__ http://lericson.blogg.se/code/2008/november/pylibmc-051.html
__ http://amix.dk/blog/viewEntry/19471
Installation
============
Building needs libmemcached and optionally zlib, the path to which can be
specified using command-line options to ``setup.py``
``--with-libmemcached=DIR``
Build against libmemcached in DIR
``--with-zlib=DIR``
Build against zlib in DIR
``--without-zlib``
Disable zlib (disables compression)
So for example, if one were to use MacPorts to install libmemcached, your
libmemcached would end up in ``/opt/local``, hence
``--with-libmemcached=/opt/local``.
IRC IRC
=== ===
``#sendapatch`` on ``chat.freenode.net``. ``#sendapatch`` on ``chat.freenode.net``.
Compiling on Snow Leopard
=========================
Since, for some reason, compiling Python extensions under Mac OS X 10.6 won't
use the proper ``-arch`` flag to ``gcc``, here's how you'd do it::
$ CFLAGS="-arch x86_64" LDFLAGS="-arch x86_64" python setup.py build
Change Log Change Log
========== ==========
New in version 1.1
------------------
- Removed deprecated space-based behavior names.
- Acquire and release the GIL properly, thanks ketralnis__
- Add support for ``libmemcached 0.40``
- Included a more useful command-line interface
- Fixed handling of NUL-byte keys in ``get_multi`` in binary protocol
- Fixed some valgrind-reported memory warnings
- Fixed bogus usage of time argument for delete.
- 1.1.1: Fixed tests under Python 2.5
__ http://www.ketralnis.com/
New in version 1.0
------------------
- Lots of documentation fixes and other nice things like that.
- Nailed what appears to be the last outstanding memory leak.
- Explicitly require libmemcached 0.32 or newer.
New in version 0.9
------------------
- Added a ``get_stats`` method, which behaves exactly like
`python-memcached`'s equivalent.
- Gives the empty string for empty memcached values like `python-memcached`
does.
- Added exceptions for most `libmemcached` return codes.
- Fixed an issue with ``Client.behaviors.update``.
New in version 0.8
------------------
- Pooling helpers are now available. See ``pooling.rst`` in the distribution.
- The binary protocol is now properly exposed, simply pass ``binary=True`` to
the constructor and there you go.
- Call signatures now match `libmemcached` 0.32, but should work with older
versions. Remember to run the tests!
New in version 0.7 New in version 0.7
------------------ ------------------
- Restructured some of the code, which should yield better performance (if not - Restructured some of the code, which should yield better performance (if not
for that, it reads better.) for that, it reads better.)
- Fixed some memory leaks. - Fixed some memory leaks.
- Integrated changes from `amix.dk`, which should make pylibmc work under - Integrated changes from `amix.dk`, which should make pylibmc work under
Snow Leopard. Snow Leopard.
- Add support for the boolean datatype. - Add support for the boolean datatype.
- Improved test-runner -- now tests ``build/lib.*/_pylibmc.so`` if available, - Improved test-runner -- now tests ``build/lib.*/_pylibmc.so`` if available,
and reports some version information. and reports some version information.
- Support for x86_64 should now work completely. - Support for x86_64 should now work completely.
- Builds with Python 2.4, tests run fine, but not officially supported.
- Fixed critical bugs in behavior manipulation.
New in version 0.6 New in version 0.6
------------------ ------------------
- Added compatibility with `libmemcached` 0.26, WRT error return codes. - Added compatibility with `libmemcached` 0.26, WRT error return codes.
- Added `flush_all` and `disconnect_all` methods. - Added `flush_all` and `disconnect_all` methods.
- Now using the latest pickling protocol. - Now using the latest pickling protocol.
New in version 0.5 New in version 0.5
------------------ ------------------
- Fixed lots of memory leaks, and added support for `libmemcached` 0.23. - Fixed lots of memory leaks, and added support for `libmemcached` 0.23.
- Also made the code tighter in terms of compiler pedantics. - Also made the code tighter in terms of compiler pedantics.
New in version 0.4 New in version 0.4
------------------ ------------------
- Renamed the C module to `_pylibmc`, and added lots of `libmemcached` constants - Renamed the C module to `_pylibmc`, and added lots of `libmemcached` constants
to it, as well as implemented behaviors. to it, as well as implemented behaviors.

6
TODO
View File

@ -1,3 +1,3 @@
- make a get_stats function - double-check all INCREF and DECREFs
- add servers after object creation (also related: set_servers) - make a get_stats function
- UDP and the binary protocol - add servers after object creation (also related: set_servers)

File diff suppressed because it is too large Load Diff

View File

@ -41,129 +41,26 @@
#include <Python.h> #include <Python.h>
#include <libmemcached/memcached.h> #include <libmemcached/memcached.h>
#include "pylibmc-version.h"
/* Py_ssize_t appeared in Python 2.5. */
#ifndef PY_SSIZE_T_MAX
typedef ssize_t Py_ssize_t;
#endif
/* Server types. */ /* Server types. */
#define PYLIBMC_SERVER_TCP (1 << 0) #define PYLIBMC_SERVER_TCP (1 << 0)
#define PYLIBMC_SERVER_UDP (1 << 1) #define PYLIBMC_SERVER_UDP (1 << 1)
#define PYLIBMC_SERVER_UNIX (1 << 2) #define PYLIBMC_SERVER_UNIX (1 << 2)
/* {{{ Key flags from python-memcached /* Key flags from python-memcache. */
* Some flags (like the compression one, ZLIB) are combined with others.
*/
#define PYLIBMC_FLAG_NONE 0 #define PYLIBMC_FLAG_NONE 0
#define PYLIBMC_FLAG_PICKLE (1 << 0) #define PYLIBMC_FLAG_PICKLE (1 << 0)
#define PYLIBMC_FLAG_INTEGER (1 << 1) #define PYLIBMC_FLAG_INTEGER (1 << 1)
#define PYLIBMC_FLAG_LONG (1 << 2) #define PYLIBMC_FLAG_LONG (1 << 2)
/* Note: this is an addition! python-memcached doesn't handle bools. */ #define PYLIBMC_FLAG_BOOL (1 << 3)
#define PYLIBMC_FLAG_BOOL (1 << 4)
#define PYLIBMC_FLAG_TYPES (PYLIBMC_FLAG_PICKLE | PYLIBMC_FLAG_INTEGER | \ #define PYLIBMC_INC (1 << 0)
PYLIBMC_FLAG_LONG | PYLIBMC_FLAG_BOOL) #define PYLIBMC_DEC (1 << 1)
/* Modifier flags */
#define PYLIBMC_FLAG_ZLIB (1 << 3)
/* }}} */
typedef memcached_return (*_PylibMC_SetCommand)(memcached_st *, const char *, typedef memcached_return (*_PylibMC_SetCommand)(memcached_st *, const char *,
size_t, const char *, size_t, time_t, uint32_t); size_t, const char *, size_t, time_t, uint32_t);
typedef memcached_return (*_PylibMC_IncrCommand)(memcached_st *,
const char *, size_t, unsigned int, uint64_t*);
typedef struct {
char key[MEMCACHED_MAX_KEY];
size_t key_len;
char *value;
size_t value_len;
uint32_t flags;
} pylibmc_mget_result;
typedef struct {
char *key;
Py_ssize_t key_len;
char* value;
Py_ssize_t value_len;
time_t time;
uint32_t flags;
/* the objects that must be freed after the mset is executed */
PyObject* key_obj;
PyObject* prefixed_key_obj;
PyObject* value_obj;
/* the success of executing the mset afterwards */
int success;
} pylibmc_mset;
typedef struct {
char* key;
Py_ssize_t key_len;
_PylibMC_IncrCommand incr_func;
unsigned int delta;
uint64_t result;
} pylibmc_incr;
typedef struct {
PyObject *self;
PyObject *retval;
memcached_server_st *servers; /* DEPRECATED */
memcached_stat_st *stats;
int index;
} _PylibMC_StatsContext;
/* {{{ Exceptions */ /* {{{ Exceptions */
static PyObject *PylibMCExc_MemcachedError; static PyObject *PylibMCExc_MemcachedError;
/* Mapping of memcached_return value -> Python exception object. */
typedef struct {
memcached_return rc;
char *name;
PyObject *exc;
} PylibMC_McErr;
static PylibMC_McErr PylibMCExc_mc_errs[] = {
{ MEMCACHED_FAILURE, "Failure", NULL },
{ MEMCACHED_HOST_LOOKUP_FAILURE, "HostLookupError", NULL },
{ MEMCACHED_CONNECTION_FAILURE, "ConnectionError", NULL },
{ MEMCACHED_CONNECTION_BIND_FAILURE, "ConnectionBindError", NULL },
{ MEMCACHED_WRITE_FAILURE, "WriteError", NULL },
{ MEMCACHED_READ_FAILURE, "ReadError", NULL },
{ MEMCACHED_UNKNOWN_READ_FAILURE, "UnknownReadFailure", NULL },
{ MEMCACHED_PROTOCOL_ERROR, "ProtocolError", NULL },
{ MEMCACHED_CLIENT_ERROR, "ClientError", NULL },
{ MEMCACHED_SERVER_ERROR, "ServerError", NULL },
{ MEMCACHED_CONNECTION_SOCKET_CREATE_FAILURE, "SocketCreateError", NULL },
{ MEMCACHED_DATA_EXISTS, "DataExists", NULL },
{ MEMCACHED_DATA_DOES_NOT_EXIST, "DataDoesNotExist", NULL },
//{ MEMCACHED_NOTSTORED, "NotStored", NULL },
//{ MEMCACHED_STORED, "Stored", NULL },
{ MEMCACHED_NOTFOUND, "NotFound", NULL },
{ MEMCACHED_MEMORY_ALLOCATION_FAILURE, "AllocationError", NULL },
//{ MEMCACHED_PARTIAL_READ, "PartialRead", NULL },
{ MEMCACHED_SOME_ERRORS, "SomeErrors", NULL },
{ MEMCACHED_NO_SERVERS, "NoServers", NULL },
//{ MEMCACHED_END, "", NULL },
//{ MEMCACHED_DELETED, "", NULL },
//{ MEMCACHED_VALUE, "", NULL },
//{ MEMCACHED_STAT, "", NULL },
//{ MEMCACHED_ITEM, "", NULL },
//{ MEMCACHED_ERRNO, "", NULL },
{ MEMCACHED_FAIL_UNIX_SOCKET, "UnixSocketError", NULL },
{ MEMCACHED_NOT_SUPPORTED, "NotSupportedError", NULL },
{ MEMCACHED_FETCH_NOTFINISHED, "FetchNotFinished", NULL },
//{ MEMCACHED_TIMEOUT, "TimeoutError", NULL },
//{ MEMCACHED_BUFFERED, "Buffer, NULL },
{ MEMCACHED_BAD_KEY_PROVIDED, "BadKeyProvided", NULL },
{ MEMCACHED_INVALID_HOST_PROTOCOL, "InvalidHostProtocolError", NULL },
//{ MEMCACHED_SERVER_MARKED_DEAD,
{ MEMCACHED_UNKNOWN_STAT_KEY, "UnknownStatKey", NULL },
//{ MEMCACHED_E2BIG, "TooBigError", NULL },
{ 0, NULL, NULL }
};
/* }}} */ /* }}} */
/* {{{ Behavior statics */ /* {{{ Behavior statics */
@ -173,33 +70,27 @@ typedef struct {
} PylibMC_Behavior; } PylibMC_Behavior;
static PylibMC_Behavior PylibMC_behaviors[] = { static PylibMC_Behavior PylibMC_behaviors[] = {
{ MEMCACHED_BEHAVIOR_NO_BLOCK, "no_block" }, { MEMCACHED_BEHAVIOR_NO_BLOCK, "no block" },
{ MEMCACHED_BEHAVIOR_TCP_NODELAY, "tcp_nodelay" }, { MEMCACHED_BEHAVIOR_TCP_NODELAY, "tcp_nodelay" },
{ MEMCACHED_BEHAVIOR_HASH, "hash" }, { MEMCACHED_BEHAVIOR_HASH, "hash" },
{ MEMCACHED_BEHAVIOR_KETAMA_HASH, "ketama_hash" },
{ MEMCACHED_BEHAVIOR_KETAMA, "ketama" }, { MEMCACHED_BEHAVIOR_KETAMA, "ketama" },
{ MEMCACHED_BEHAVIOR_KETAMA_WEIGHTED, "ketama_weighted" },
{ MEMCACHED_BEHAVIOR_DISTRIBUTION, "distribution" }, { MEMCACHED_BEHAVIOR_DISTRIBUTION, "distribution" },
{ MEMCACHED_BEHAVIOR_SOCKET_SEND_SIZE, "socket send size" },
{ MEMCACHED_BEHAVIOR_SOCKET_RECV_SIZE, "socket recv size" },
{ MEMCACHED_BEHAVIOR_CACHE_LOOKUPS, "cache_lookups" }, { MEMCACHED_BEHAVIOR_CACHE_LOOKUPS, "cache_lookups" },
{ MEMCACHED_BEHAVIOR_SUPPORT_CAS, "cas" }, { MEMCACHED_BEHAVIOR_SUPPORT_CAS, "support_cas" },
{ MEMCACHED_BEHAVIOR_POLL_TIMEOUT, "poll_timeout" },
{ MEMCACHED_BEHAVIOR_BUFFER_REQUESTS, "buffer_requests" }, { MEMCACHED_BEHAVIOR_BUFFER_REQUESTS, "buffer_requests" },
{ MEMCACHED_BEHAVIOR_VERIFY_KEY, "verify_keys" }, { MEMCACHED_BEHAVIOR_SORT_HOSTS, "sort_hosts" },
{ MEMCACHED_BEHAVIOR_VERIFY_KEY, "verify_key" },
{ MEMCACHED_BEHAVIOR_CONNECT_TIMEOUT, "connect_timeout" }, { MEMCACHED_BEHAVIOR_CONNECT_TIMEOUT, "connect_timeout" },
{ MEMCACHED_BEHAVIOR_SND_TIMEOUT, "send_timeout" }, { MEMCACHED_BEHAVIOR_RETRY_TIMEOUT, "retry_timeout" },
{ MEMCACHED_BEHAVIOR_RCV_TIMEOUT, "receive_timeout" }, { MEMCACHED_BEHAVIOR_KETAMA_WEIGHTED, "ketama_weighted" },
{ MEMCACHED_BEHAVIOR_SERVER_FAILURE_LIMIT, "failure_limit" }, { MEMCACHED_BEHAVIOR_KETAMA_HASH, "ketama_hash" },
{ MEMCACHED_BEHAVIOR_IO_MSG_WATERMARK, "_io_msg_watermark" }, { MEMCACHED_BEHAVIOR_BINARY_PROTOCOL, "binary_protocol" },
{ MEMCACHED_BEHAVIOR_IO_BYTES_WATERMARK, "_io_bytes_watermark" }, { MEMCACHED_BEHAVIOR_SND_TIMEOUT, "snd_timeout" },
{ MEMCACHED_BEHAVIOR_IO_KEY_PREFETCH, "_io_key_prefetch" }, { MEMCACHED_BEHAVIOR_RCV_TIMEOUT, "rcv_timeout" },
{ MEMCACHED_BEHAVIOR_HASH_WITH_PREFIX_KEY, "_hash_with_prefix_key" }, { MEMCACHED_BEHAVIOR_SERVER_FAILURE_LIMIT, "server_failure_limit" },
{ MEMCACHED_BEHAVIOR_NOREPLY, "_noreply" },
{ MEMCACHED_BEHAVIOR_AUTO_EJECT_HOSTS, "_auto_eject_hosts" },
{ MEMCACHED_BEHAVIOR_NUMBER_OF_REPLICAS, "_number_of_replicas" },
{ MEMCACHED_BEHAVIOR_SORT_HOSTS, "_sort_hosts" },
{ MEMCACHED_BEHAVIOR_RETRY_TIMEOUT, "_retry_timeout" },
{ MEMCACHED_BEHAVIOR_POLL_TIMEOUT, "_poll_timeout" },
{ MEMCACHED_BEHAVIOR_SOCKET_SEND_SIZE, "_socket_send_size" },
{ MEMCACHED_BEHAVIOR_SOCKET_RECV_SIZE, "_socket_recv_size" },
{ 0, NULL } { 0, NULL }
}; };
@ -211,10 +102,8 @@ static PylibMC_Behavior PylibMC_hashers[] = {
{ MEMCACHED_HASH_FNV1A_64, "fnv1a_64" }, { MEMCACHED_HASH_FNV1A_64, "fnv1a_64" },
{ MEMCACHED_HASH_FNV1_32, "fnv1_32" }, { MEMCACHED_HASH_FNV1_32, "fnv1_32" },
{ MEMCACHED_HASH_FNV1A_32, "fnv1a_32" }, { MEMCACHED_HASH_FNV1A_32, "fnv1a_32" },
{ MEMCACHED_HASH_MURMUR, "murmur" },
#ifdef MEMCACHED_HASH_HSIEH
{ MEMCACHED_HASH_HSIEH, "hsieh" }, { MEMCACHED_HASH_HSIEH, "hsieh" },
#endif { MEMCACHED_HASH_MURMUR, "murmur" },
{ 0, NULL } { 0, NULL }
}; };
@ -238,59 +127,33 @@ static PylibMC_Client *PylibMC_ClientType_new(PyTypeObject *, PyObject *,
static void PylibMC_ClientType_dealloc(PylibMC_Client *); static void PylibMC_ClientType_dealloc(PylibMC_Client *);
static int PylibMC_Client_init(PylibMC_Client *, PyObject *, PyObject *); static int PylibMC_Client_init(PylibMC_Client *, PyObject *, PyObject *);
static PyObject *PylibMC_Client_get(PylibMC_Client *, PyObject *arg); static PyObject *PylibMC_Client_get(PylibMC_Client *, PyObject *arg);
static PyObject *PylibMC_Client_gets(PylibMC_Client *, PyObject *arg);
static PyObject *PylibMC_Client_set(PylibMC_Client *, PyObject *, PyObject *); static PyObject *PylibMC_Client_set(PylibMC_Client *, PyObject *, PyObject *);
static PyObject *PylibMC_Client_replace(PylibMC_Client *, PyObject *, PyObject *); static PyObject *PylibMC_Client_replace(PylibMC_Client *, PyObject *, PyObject *);
static PyObject *PylibMC_Client_add(PylibMC_Client *, PyObject *, PyObject *); static PyObject *PylibMC_Client_add(PylibMC_Client *, PyObject *, PyObject *);
static PyObject *PylibMC_Client_prepend(PylibMC_Client *, PyObject *, PyObject *); static PyObject *PylibMC_Client_prepend(PylibMC_Client *, PyObject *, PyObject *);
static PyObject *PylibMC_Client_append(PylibMC_Client *, PyObject *, PyObject *); static PyObject *PylibMC_Client_append(PylibMC_Client *, PyObject *, PyObject *);
static PyObject *PylibMC_Client_cas(PylibMC_Client *, PyObject *, PyObject *);
static PyObject *PylibMC_Client_delete(PylibMC_Client *, PyObject *); static PyObject *PylibMC_Client_delete(PylibMC_Client *, PyObject *);
static PyObject *PylibMC_Client_incr(PylibMC_Client *, PyObject *); static PyObject *PylibMC_Client_incr(PylibMC_Client *, PyObject *);
static PyObject *PylibMC_Client_decr(PylibMC_Client *, PyObject *); static PyObject *PylibMC_Client_decr(PylibMC_Client *, PyObject *);
static PyObject *PylibMC_Client_incr_multi(PylibMC_Client*, PyObject*, PyObject*);
static PyObject *PylibMC_Client_get_multi(PylibMC_Client *, PyObject *, PyObject *); static PyObject *PylibMC_Client_get_multi(PylibMC_Client *, PyObject *, PyObject *);
static PyObject *PylibMC_Client_set_multi(PylibMC_Client *, PyObject *, PyObject *); static PyObject *PylibMC_Client_set_multi(PylibMC_Client *, PyObject *, PyObject *);
static PyObject *PylibMC_Client_add_multi(PylibMC_Client *, PyObject *, PyObject *);
static PyObject *PylibMC_Client_delete_multi(PylibMC_Client *, PyObject *, PyObject *); static PyObject *PylibMC_Client_delete_multi(PylibMC_Client *, PyObject *, PyObject *);
static PyObject *PylibMC_Client_get_behaviors(PylibMC_Client *); static PyObject *PylibMC_Client_get_behaviors(PylibMC_Client *);
static PyObject *PylibMC_Client_set_behaviors(PylibMC_Client *, PyObject *); static PyObject *PylibMC_Client_set_behaviors(PylibMC_Client *, PyObject *);
static PyObject *PylibMC_Client_get_stats(PylibMC_Client *, PyObject *);
static PyObject *PylibMC_Client_flush_all(PylibMC_Client *, PyObject *, PyObject *); static PyObject *PylibMC_Client_flush_all(PylibMC_Client *, PyObject *, PyObject *);
static PyObject *PylibMC_Client_disconnect_all(PylibMC_Client *); static PyObject *PylibMC_Client_disconnect_all(PylibMC_Client *);
static PyObject *PylibMC_Client_clone(PylibMC_Client *);
static PyObject *PylibMC_ErrFromMemcached(PylibMC_Client *, const char *, static PyObject *PylibMC_ErrFromMemcached(PylibMC_Client *, const char *,
memcached_return); memcached_return);
static PyObject *_PylibMC_Unpickle(const char *, size_t); static PyObject *_PylibMC_Unpickle(const char *, size_t);
static PyObject *_PylibMC_Pickle(PyObject *); static PyObject *_PylibMC_Pickle(PyObject *);
static int _PylibMC_CheckKey(PyObject *); static int _PylibMC_CheckKey(PyObject *);
static int _PylibMC_CheckKeyStringAndSize(char *, Py_ssize_t); static int _PylibMC_CheckKeyStringAndSize(char *, Py_ssize_t);
static int _PylibMC_SerializeValue(PyObject* key_obj,
PyObject* key_prefix,
PyObject* value_obj,
time_t time,
pylibmc_mset* serialized);
static void _PylibMC_FreeMset(pylibmc_mset*);
static PyObject *_PylibMC_RunSetCommandSingle(PylibMC_Client *self,
_PylibMC_SetCommand f, char *fname, PyObject *args, PyObject *kwds);
static PyObject *_PylibMC_RunSetCommandMulti(PylibMC_Client* self,
_PylibMC_SetCommand f, char *fname, PyObject *args, PyObject *kwds);
static bool _PylibMC_RunSetCommand(PylibMC_Client* self,
_PylibMC_SetCommand f, char *fname,
pylibmc_mset* msets, size_t nkeys,
size_t min_compress, size_t custom_flag);
static int _PylibMC_Deflate(char* value, size_t value_len,
char** result, size_t *result_len);
static bool _PylibMC_IncrDecr(PylibMC_Client*, pylibmc_incr*, size_t);
/* }}} */ /* }}} */
/* {{{ Type's method table */ /* {{{ Type's method table */
static PyMethodDef PylibMC_ClientType_methods[] = { static PyMethodDef PylibMC_ClientType_methods[] = {
{"get", (PyCFunction)PylibMC_Client_get, METH_O, {"get", (PyCFunction)PylibMC_Client_get, METH_O,
"Retrieve a key from a memcached."}, "Retrieve a key from a memcached."},
{"gets", (PyCFunction)PylibMC_Client_gets, METH_O,
"Retrieve a key and cas_id from a memcached."},
{"set", (PyCFunction)PylibMC_Client_set, METH_VARARGS|METH_KEYWORDS, {"set", (PyCFunction)PylibMC_Client_set, METH_VARARGS|METH_KEYWORDS,
"Set a key unconditionally."}, "Set a key unconditionally."},
{"replace", (PyCFunction)PylibMC_Client_replace, METH_VARARGS|METH_KEYWORDS, {"replace", (PyCFunction)PylibMC_Client_replace, METH_VARARGS|METH_KEYWORDS,
@ -301,37 +164,26 @@ static PyMethodDef PylibMC_ClientType_methods[] = {
"Prepend data to a key."}, "Prepend data to a key."},
{"append", (PyCFunction)PylibMC_Client_append, METH_VARARGS|METH_KEYWORDS, {"append", (PyCFunction)PylibMC_Client_append, METH_VARARGS|METH_KEYWORDS,
"Append data to a key."}, "Append data to a key."},
{"cas", (PyCFunction)PylibMC_Client_cas, METH_VARARGS|METH_KEYWORDS,
"Attempt to compare-and-store a key by CAS ID."},
{"delete", (PyCFunction)PylibMC_Client_delete, METH_VARARGS, {"delete", (PyCFunction)PylibMC_Client_delete, METH_VARARGS,
"Delete a key."}, "Delete a key."},
{"incr", (PyCFunction)PylibMC_Client_incr, METH_VARARGS, {"incr", (PyCFunction)PylibMC_Client_incr, METH_VARARGS,
"Increment a key by a delta."}, "Increment a key by a delta."},
{"decr", (PyCFunction)PylibMC_Client_decr, METH_VARARGS, {"decr", (PyCFunction)PylibMC_Client_decr, METH_VARARGS,
"Decrement a key by a delta."}, "Decrement a key by a delta."},
{"incr_multi", (PyCFunction)PylibMC_Client_incr_multi, METH_VARARGS|METH_KEYWORDS,
"Increment more than one key by a delta."},
{"get_multi", (PyCFunction)PylibMC_Client_get_multi, {"get_multi", (PyCFunction)PylibMC_Client_get_multi,
METH_VARARGS|METH_KEYWORDS, "Get multiple keys at once."}, METH_VARARGS|METH_KEYWORDS, "Get multiple keys at once."},
{"set_multi", (PyCFunction)PylibMC_Client_set_multi, {"set_multi", (PyCFunction)PylibMC_Client_set_multi,
METH_VARARGS|METH_KEYWORDS, "Set multiple keys at once."}, METH_VARARGS|METH_KEYWORDS, "Set multiple keys at once."},
{"add_multi", (PyCFunction)PylibMC_Client_add_multi,
METH_VARARGS|METH_KEYWORDS, "Add multiple keys at once."},
{"delete_multi", (PyCFunction)PylibMC_Client_delete_multi, {"delete_multi", (PyCFunction)PylibMC_Client_delete_multi,
METH_VARARGS|METH_KEYWORDS, "Delete multiple keys at once."}, METH_VARARGS|METH_KEYWORDS, "Delete multiple keys at once."},
{"get_behaviors", (PyCFunction)PylibMC_Client_get_behaviors, METH_NOARGS, {"get_behaviors", (PyCFunction)PylibMC_Client_get_behaviors, METH_NOARGS,
"Get behaviors dict."}, "Get behaviors dict."},
{"set_behaviors", (PyCFunction)PylibMC_Client_set_behaviors, METH_O, {"set_behaviors", (PyCFunction)PylibMC_Client_set_behaviors, METH_O,
"Set behaviors dict."}, "Set behaviors dict."},
{"get_stats", (PyCFunction)PylibMC_Client_get_stats,
METH_VARARGS, "Retrieve statistics from all memcached servers."},
{"flush_all", (PyCFunction)PylibMC_Client_flush_all, {"flush_all", (PyCFunction)PylibMC_Client_flush_all,
METH_VARARGS|METH_KEYWORDS, "Flush all data on all servers."}, METH_VARARGS|METH_KEYWORDS, "Flush all data on all servers."},
{"disconnect_all", (PyCFunction)PylibMC_Client_disconnect_all, METH_NOARGS, {"disconnect_all", (PyCFunction)PylibMC_Client_disconnect_all, METH_NOARGS,
"Disconnect from all servers and reset own state."}, "Disconnect from all servers and reset own state."},
{"clone", (PyCFunction)PylibMC_Client_clone, METH_NOARGS,
"Clone this client entirely such that it is safe to access from "
"another thread. This creates a new connection."},
{NULL, NULL, 0, NULL} {NULL, NULL, 0, NULL}
}; };
/* }}} */ /* }}} */
@ -379,7 +231,7 @@ static PyTypeObject PylibMC_ClientType = {
0, 0,
(initproc)PylibMC_Client_init, (initproc)PylibMC_Client_init,
0, 0,
(newfunc)PylibMC_ClientType_new, //PyType_GenericNew, (newfunc)PylibMC_ClientType_new,
0, 0,
0, 0,
0, 0,

67
cmemcache_tests.py Normal file
View File

@ -0,0 +1,67 @@
import pylibmc
import unittest
import pickle
class TestCmemcached(unittest.TestCase):
def setUp(self):
self.mc = pylibmc.Client(["127.0.0.1:11211"])
#self.mc = pylibmc.Client(["127.0.0.1:11211","127.0.0.1:11212", "127.0.0.1:11213", "127.0.0.1:11214", "127.0.0.1:11215"])
def testSetAndGet(self):
self.mc.set("num12345", 12345)
self.assertEqual(self.mc.get("num12345"), 12345)
self.mc.set("str12345", "12345")
self.assertEqual(self.mc.get("str12345"), "12345")
def testDelete(self):
self.mc.set("str12345", "12345")
#delete return True on success, otherwise False
ret = self.mc.delete("str12345")
self.assertEqual(self.mc.get("str12345"), None)
self.assertEqual(ret, True)
ret = self.mc.delete("hello world")
self.assertEqual(ret, False)
def testGetMulti(self):
self.mc.set("a", "valueA")
self.mc.set("b", "valueB")
self.mc.set("c", "valueC")
result = self.mc.get_multi(["a", "b", "c", "", "hello world"])
self.assertEqual(result, {'a':'valueA', 'b':'valueB', 'c':'valueC'})
def testBigGetMulti(self):
count = 10
keys = ['key%d' % i for i in xrange(count)]
pairs = zip(keys, ['value%d' % i for i in xrange(count)])
for key, value in pairs:
self.mc.set(key, value)
result = self.mc.get_multi(keys)
assert result == dict(pairs)
def testFunnyDelete(self):
result= self.mc.delete("")
self.assertEqual(result, False)
def testAppend(self):
self.mc.delete("a")
self.mc.set("a", "I ")
ret = self.mc.append("a", "Do")
print ret
result = self.mc.get("a")
print result
self.assertEqual(result, "I Do")
def testPrepend(self):
self.mc.delete("a")
self.mc.set("a", "Do")
ret = self.mc.prepend("a", "I ")
print ret
result = self.mc.get("a")
print result
self.assertEqual(result, "I Do")
if __name__ == '__main__':
unittest.main()

View File

@ -1,8 +0,0 @@
The List of Honored Men
=======================
* Ludvig Ericson <ludvig@lericson.se>
* ketralnis <ketralnis@reddit.com>
* Ruda Moura <ruda.moura@corp.terra.com.br>
* Noah Silas <noah@mahalo.com>
* Johan Bergström <johan@bergstroem.nu>

View File

@ -1,82 +0,0 @@
========================
Pooling with `pylibmc`
========================
:Author: Ludvig Ericson <ludvig circled-a lericson dot se>
:See also: `Pooling with pylibmc`__ (this document)
:See also: `Pooling with pylibmc pt. 2`__ (follow-up)
__ http://lericson.blogg.se/code/2009/september/draft-sept-20-2009.html
__ http://lericson.blogg.se/code/2009/september/pooling-with-pylibmc-pt-2.html
.. This is really a blog post, I do write them in ReST occasionally. Provided
here for the sake of convenience.
I was discussing how to implement pooling for `pylibmc` when I realized what
`libmemcachedutil`'s pooling is - or rather, what it isn't.
It's not a magical solution for concurrently doing anything at all, it's not
anything like that -- it just helps you with thread-safety.
In Python, however, we've got the global interpreter lock, the GIL. This lock
must always be held by the thread that is dealing with anything Python. The
Python interpreter itself isn't thread-safe, or rather, it is with the GIL.
This means that whenever Python code is running, you'll be sure to have
exclusive access to all of Python's memory (unless something is misbehaving.)
In turn, this means that the usecase for using `libmemcachedutil` in a Python
library is rather slim.
Let's have a look at some code for doing the equivalent in pure Python. This is
a Werkzeug-based WSGI application which would be run in multiple threads,
concurrently:
.. code-block:: python
# Configuration
n_threads = 12
mc_addrs = "10.0.1.1", "10.0.1.2", "10.0.1.3"
mc_pool_size = n_threads
# Application
import pylibmc
from contextlib import contextmanager
from pprint import pformat
from werkzeug import Request, Response
from werkzeug.exceptions import NotFound
class ClientPool(list):
@contextmanager
def reserve(self):
mc = self.pop()
try:
yield mc
finally:
self.append(mc)
mc = pylibmc.Client(mc_addrs)
mc_pool = ClientPool(mc.clone() for i in xrange(mc_pool_size))
@Request.application
def my_app(request):
with mc_pool.reserve() as mc:
key = request.path[1:].encode("ascii")
val = mc.get(key)
if not val:
return NotFound(key)
return Response(pformat(val))
It's fully-functional example of how one could implement pooling with
`pylibmc`, and very much so in the same way that people do with
`libmemcachedutil`. To start it, you could use Spawning like so:
``spawn -s 1 -t 12 my_wsgi_app.my_app``
I don't know if I think the above methodology is the best one though, another
possibility is to have a dict with thread names as keys and client objects for
values, then, each thread would look up its own client object in the dict on
each request, and if none exists, it clones a master just like the pooling
thing above.
It'd be neat if there was a generic Python API for doing any variant of
pooling, per-thread or the list-based version, and then you'd be able to switch
between them seamlessly. Hm.

View File

@ -1 +0,0 @@
#define PYLIBMC_VERSION "1.1.1"

View File

@ -1,22 +1,57 @@
"""Python-level wrapper client""" """`python-memcached`-compatible wrapper around `_pylibmc`.
The interface is pretty much exactly the same as python-memcached, with some
minor differences. If you should happen to spot any, file a bug!
>>> mc = Client(["127.0.0.1"])
>>> b = mc.behaviors
>>> b.keys()
['hash', 'connect timeout', 'cache lookups', 'buffer requests', 'verify key', 'support cas', 'poll timeout', 'no block', 'tcp nodelay', 'distribution', 'sort hosts']
>>> b["hash"]
'default'
>>> b["hash"] = 'fnv1a_32'
>>> mc.behaviors["hash"]
'fnv1a_32'
"""
import _pylibmc import _pylibmc
from .consts import (hashers, distributions,
hashers_rvs, distributions_rvs, __all__ = ["hashers", "distributions", "Client"]
BehaviorDict) __version__ = "0.7.3"
hashers = {}
distributions = {}
# Not the prettiest way of doing things, but works well.
for name in dir(_pylibmc):
if name.startswith("hash_"):
hashers[name[5:]] = getattr(_pylibmc, name)
elif name.startswith("distribution_"):
distributions[name[13:].replace("_", " ")] = getattr(_pylibmc, name)
hashers_rvs = dict((v, k) for (k, v) in hashers.iteritems())
distributions_rvs = dict((v, k) for (k, v) in distributions.iteritems())
class BehaviorDict(dict):
def __init__(self, client, *args, **kwds):
super(BehaviorDict, self).__init__(*args, **kwds)
self.client = client
def __setitem__(self, name, value):
super(BehaviorDict, self).__setitem__(name, value)
self.client.behaviors = self
def update(self, *args, **kwds):
super(BehaviorDict, self).update(*args, **kwds)
self.client.behaviors = self
class Client(_pylibmc.client): class Client(_pylibmc.client):
def __init__(self, servers, binary=False): def __init__(self, servers, *args, **kwds):
"""Initialize a memcached client instance. """Initialize a memcached client instance.
This connects to the servers in *servers*, which will default to being This connects to the servers in *servers*, which will default to being
TCP servers. If it looks like a filesystem path, a UNIX socket. If TCP servers. If it looks like a filesystem path, a UNIX socket. If
prefixed with `udp:`, a UDP connection. prefixed with `udp:`, a UDP connection.
If *binary* is True, the binary memcached protocol is used.
""" """
self.binary = binary
self.addresses = list(servers)
addr_tups = [] addr_tups = []
for server in servers: for server in servers:
addr = server addr = server
@ -37,16 +72,7 @@ class Client(_pylibmc.client):
else: else:
stype = _pylibmc.server_type_tcp stype = _pylibmc.server_type_tcp
addr_tups.append((stype, addr, port)) addr_tups.append((stype, addr, port))
super(Client, self).__init__(servers=addr_tups, binary=binary) super(Client, self).__init__(addr_tups)
def __repr__(self):
return "%s(%r, binary=%r)" % (self.__class__.__name__,
self.addresses, self.binary)
def __str__(self):
addrs = ", ".join(map(str, self.addresses))
return "<%s for %s, binary=%r>" % (self.__class__.__name__,
addrs, self.binary)
def get_behaviors(self): def get_behaviors(self):
"""Gets the behaviors from the underlying C client instance. """Gets the behaviors from the underlying C client instance.
@ -54,10 +80,10 @@ class Client(_pylibmc.client):
Reverses the integer constants for `hash` and `distribution` into more Reverses the integer constants for `hash` and `distribution` into more
understandable string values. See *set_behaviors* for info. understandable string values. See *set_behaviors* for info.
""" """
bvrs = super(Client, self).get_behaviors() behaviors = super(Client, self).get_behaviors()
bvrs["hash"] = hashers_rvs[bvrs["hash"]] behaviors["hash"] = hashers_rvs[behaviors["hash"]]
bvrs["distribution"] = distributions_rvs[bvrs["distribution"]] behaviors["distribution"] = distributions_rvs[behaviors["distribution"]]
return BehaviorDict(self, bvrs) return BehaviorDict(self, behaviors)
def set_behaviors(self, behaviors): def set_behaviors(self, behaviors):
"""Sets the behaviors on the underlying C client instance. """Sets the behaviors on the underlying C client instance.
@ -70,21 +96,14 @@ class Client(_pylibmc.client):
This also happens for `distribution`. This also happens for `distribution`.
""" """
behaviors = behaviors.copy() behaviors = behaviors.copy()
if behaviors.get("hash") is not None: if behaviors.get("hash", None) in hashers:
behaviors["hash"] = hashers[behaviors["hash"]] behaviors["hash"] = hashers[behaviors["hash"]]
if behaviors.get("ketama_hash") is not None: if behaviors.get("distribution") in distributions:
behaviors["ketama_hash"] = hashers[behaviors["ketama_hash"]]
if behaviors.get("distribution") is not None:
behaviors["distribution"] = distributions[behaviors["distribution"]] behaviors["distribution"] = distributions[behaviors["distribution"]]
return super(Client, self).set_behaviors(behaviors) return super(Client, self).set_behaviors(behaviors)
behaviors = property(get_behaviors, set_behaviors) behaviors = property(get_behaviors, set_behaviors)
@property
def behaviours(self):
raise AttributeError("nobody uses british spellings")
def clone(self): if __name__ == "__main__":
obj = super(Client, self).clone() import doctest
obj.addresses = list(self.addresses) doctest.testmod()
obj.binary = self.binary
return obj

View File

@ -1,73 +0,0 @@
"""Snappy libmemcached wrapper
pylibmc is a Python wrapper around TangentOrg's libmemcached library.
The interface is intentionally made as close to python-memcached as possible,
so that applications can drop-in replace it.
Example usage
=============
Create a connection and configure it::
>>> import pylibmc
>>> mc = pylibmc.Client(["127.0.0.1"], binary=True)
>>> mc.behaviors = {"tcp_nodelay": True, "ketama": True}
Basic operation::
>>> mc.set("some_key", "Some value")
True
>>> value = mc.get("some_key")
>>> value
'Some value'
>>> mc.set("another_key", 3)
True
>>> mc.delete("another_key")
True
>>> mc.set("key", "1") # str or int is fine
True
Atomic increments and decrements::
>>> mc.incr("key")
2L
>>> mc.decr("key")
1L
Batch operation::
>>> mc.get_multi(["key", "another_key"])
{'key': '1'}
>>> mc.set_multi({"cats": ["on acid", "furry"], "dogs": True})
[]
>>> mc.get_multi(["cats", "dogs"])
{'cats': ['on acid', 'furry'], 'dogs': True}
>>> mc.delete_multi(["cats", "dogs", "nonextant"])
False
>>> mc.add_multi({"cats": ["on acid", "furry"], "dogs": True})
[]
>>> mc.get_multi(["cats", "dogs"])
{'cats': ['on acid', 'furry'], 'dogs': True}
>>> mc.add_multi({"cats": "not set", "dogs": "definitely not set", "bacon": "yummy"})
['cats', 'dogs']
>>> mc.get_multi(["cats", "dogs", "bacon"])
{'cats': ['on acid', 'furry'], 'bacon': 'yummy', 'dogs': True}
>>> mc.delete_multi(["cats", "dogs", "bacon"])
True
Further Reading
===============
See http://sendapatch.se/projects/pylibmc/
"""
import _pylibmc
from .consts import hashers, distributions
from .client import Client
from .pools import ClientPool, ThreadMappedPool
support_compression = _pylibmc.support_compression
__version__ = _pylibmc.__version__
__all__ = ["hashers", "distributions", "Client",
"ClientPool", "ThreadMappedPool"]

View File

@ -1,48 +0,0 @@
"""Interactive shell"""
import sys
import code
import random
import pylibmc
tips = [
"Want to use 127.0.0.1? Just hit Enter immediately.",
"This was supposed to be a list of tips but I...",
"I don't really know what to write here.",
"Really, hit Enter immediately and you'll connect to 127.0.0.1.",
"Did you know there's a --binary flag? Try it!",
"Want to use binary mode? Pass --binary as a sole argument."
]
def print_header(outf=sys.stdout):
outf.write("pylibmc interactive shell\n\n")
outf.write("Input list of servers, terminating by a blank line.\n")
outf.write(random.choice(tips) + "\n")
def collect_servers():
in_addr = raw_input("Address [127.0.0.1]: ")
while in_addr:
in_addr = raw_input("Address [<stop>]: ")
if in_addr:
yield in_addr
else:
yield "127.0.0.1"
banner = ("\nmc client available as `mc`\n"
"memface available as mf")
def interact(servers, banner=banner, binary=False):
mc = pylibmc.Client(servers, binary=binary)
mf = pylibmc.Memface(servers, binary=binary)
local = {"pylibmc": pylibmc,
"mf": mf, "mc": mc}
code.interact(banner=banner, local=local)
def main():
binary = False
if sys.argv[1:] == ["--binary"]:
binary = True
print_header()
interact(list(collect_servers()), binary=binary)
if __name__ == "__main__":
main()

View File

@ -1,40 +0,0 @@
"""Constants and functionality related to them"""
import _pylibmc
#: Mapping of
errors = tuple(e for (n, e) in _pylibmc.exceptions)
# *Cough* Uhm, not the prettiest of things but this unpacks all exception
# objects and sets them on the package module object currently constructed.
import sys
modpkg = sys.modules[__package__]
modself = sys.modules[__package__]
for name, exc in _pylibmc.exceptions:
setattr(modself, name, exc)
setattr(modpkg, name, exc)
hashers, hashers_rvs = {}, {}
distributions, distributions_rvs = {}, {}
# Not the prettiest way of doing things, but works well.
for name in dir(_pylibmc):
if name.startswith("hash_"):
key, value = name[5:], getattr(_pylibmc, name)
hashers[key] = value
hashers_rvs[value] = key
elif name.startswith("distribution_"):
key, value = name[13:].replace("_", " "), getattr(_pylibmc, name)
distributions[key] = value
distributions_rvs[value] = key
class BehaviorDict(dict):
def __init__(self, client, *args, **kwds):
super(BehaviorDict, self).__init__(*args, **kwds)
self.client = client
def __setitem__(self, name, value):
super(BehaviorDict, self).__setitem__(name, value)
self.client.set_behaviors({name: value})
def update(self, d):
super(BehaviorDict, self).update(d)
self.client.set_behaviors(d.copy())

View File

@ -1,97 +0,0 @@
"""Pooling"""
from __future__ import with_statement
from contextlib import contextmanager
from Queue import Queue
class ClientPool(Queue):
"""Client pooling helper.
This is mostly useful in threaded environments, because a client isn't
thread-safe at all. Instead, what you want to do is have each thread use
its own client, but you don't want to reconnect these all the time.
The solution is a pool, and this class is a helper for that.
>>> mc = Client(["127.0.0.1"])
>>> pool = ClientPool()
>>> pool.fill(mc, 4)
>>> with pool.reserve() as mc:
... mc.set("hi", "ho")
... mc.delete("hi")
...
True
True
"""
def __init__(self, mc=None, n_slots=None):
Queue.__init__(self, n_slots)
if mc is not None:
self.fill(mc, n_slots)
@contextmanager
def reserve(self, timeout=None):
"""Context manager for reserving a client from the pool.
If *timeout* is given, it specifiecs how long to wait for a client to
become available.
"""
mc = self.get(True, timeout=timeout)
try:
yield mc
finally:
self.put(mc)
def fill(self, mc, n_slots):
"""Fill *n_slots* of the pool with clones of *mc*."""
for i in xrange(n_slots):
self.put(mc.clone())
class ThreadMappedPool(dict):
"""Much like the *ClientPool*, helps you with pooling.
In a threaded environment, you'd most likely want to have a client per
thread. And there'd be no harm in one thread keeping the same client at all
times. So, why not map threads to clients? That's what this class does.
If a client is reserved, this class checks for a key based on the current
thread, and if none exists, clones the master client and inserts that key.
>>> mc = Client(["127.0.0.1"])
>>> pool = ThreadMappedPool(mc)
>>> with pool.reserve() as mc:
... mc.set("hi", "ho")
... mc.delete("hi")
...
True
True
"""
def __new__(cls, master):
return super(ThreadMappedPool, cls).__new__(cls)
def __init__(self, master):
self.master = master
@contextmanager
def reserve(self):
"""Reserve a client.
Creates a new client based on the master client if none exists for the
current thread.
"""
key = thread.get_ident()
mc = self.pop(key, None)
if mc is None:
mc = self.master.clone()
try:
yield mc
finally:
self[key] = mc
# This makes sure ThreadMappedPool doesn't exist with non-thread Pythons.
try:
import thread
except ImportError:
ThreadMappedPool = None

View File

@ -1,77 +1,23 @@
import os import os
import sys
from distutils.core import setup, Extension from distutils.core import setup, Extension
# --with-zlib: use zlib for compressing and decompressing
# --without-zlib: ^ negated
# --with-zlib=<dir>: path to zlib if needed
# --with-libmemcached=<dir>: path to libmemcached package if needed
cmd = None
use_zlib = True
pkgdirs = [] # incdirs and libdirs get these
libs = ["memcached"]
defs = []
incdirs = [] incdirs = []
libdirs = [] libdirs = []
def append_env(L, e): if "LIBMEMCACHED_DIR" in os.environ:
v = os.environ.get(e) libdir = os.path.normpath(os.environ["LIBMEMCACHED_DIR"])
if v and os.path.exists(v): incdirs.append(os.path.join(libdir, "include"))
L.append(v) libdirs.append(os.path.join(libdir, "lib"))
append_env(pkgdirs, "LIBMEMCACHED")
append_env(pkgdirs, "ZLIB")
# Hack up sys.argv, yay
unprocessed = []
for arg in sys.argv[1:]:
if arg == "--with-zlib":
use_zlib = True
continue
elif arg == "--without-zlib":
use_zlib = False
continue
elif arg == "--gen-setup":
cmd = arg[2:]
elif "=" in arg:
if arg.startswith("--with-libmemcached=") or \
arg.startswith("--with-zlib="):
pkgdirs.append(arg.split("=", 1)[1])
continue
unprocessed.append(arg)
sys.argv[1:] = unprocessed
for pkgdir in pkgdirs:
incdirs.append(os.path.join(pkgdir, "include"))
libdirs.append(os.path.join(pkgdir, "lib"))
if use_zlib:
libs.append("z")
defs.append(("USE_ZLIB", None))
pylibmc_ext = Extension("_pylibmc", ["_pylibmcmodule.c"],
libraries=libs, include_dirs=incdirs,
library_dirs=libdirs, define_macros=defs)
# Hidden secret: if environment variable GEN_SETUP is set, generate Setup file.
if cmd == "gen-setup":
line = " ".join((
pylibmc_ext.name,
" ".join("-l" + lib for lib in pylibmc_ext.libraries),
" ".join("-I" + incdir for incdir in pylibmc_ext.include_dirs),
" ".join("-L" + libdir for libdir in pylibmc_ext.library_dirs),
" ".join("-D" + name + ("=" + str(value), "")[value is None] for (name, value) in pylibmc_ext.define_macros)))
open("Setup", "w").write(line + "\n")
sys.exit(0)
readme_text = open("README.rst", "U").read() readme_text = open("README.rst", "U").read()
version = open("pylibmc-version.h", "U").read().strip().split("\"")[1]
setup(name="pylibmc", version=version, pylibmc_ext = Extension("_pylibmc", ["_pylibmcmodule.c"],
url="http://sendapatch.se/projects/pylibmc/", libraries=["memcached"],
include_dirs=incdirs, library_dirs=libdirs)
setup(name="pylibmc", version="0.7.3",
url="http://lericson.blogg.se/code/category/pylibmc.html",
author="Ludvig Ericson", author_email="ludvig@lericson.se", author="Ludvig Ericson", author_email="ludvig@lericson.se",
license="3-clause BSD <http://www.opensource.org/licenses/bsd-license.php>", license="3-clause BSD <http://www.opensource.org/licenses/bsd-license.php>",
description="Quick and small memcached client for Python", description="libmemcached wrapper", long_description=readme_text,
long_description=readme_text, ext_modules=[pylibmc_ext], py_modules=["pylibmc"])
ext_modules=[pylibmc_ext], packages=["pylibmc"])

355
tests.py
View File

@ -1,11 +1,7 @@
r"""Tests. They want YOU!! """Tests. They want YOU!!
Basic functionality. Basic functionality.
>>> _pylibmc.__version__ == pylibmc.__version__
True
>>> c = _pylibmc.client([test_server]) >>> c = _pylibmc.client([test_server])
>>> c.get("hello")
>>> c.set("test_key", 123) >>> c.set("test_key", 123)
True True
>>> c.get("test_key") >>> c.get("test_key")
@ -16,12 +12,6 @@ True
>>> c.get("test_key") >>> c.get("test_key")
>>> >>>
We should handle empty values just nicely.
>>> c.set("foo", "")
True
>>> c.get("foo")
''
Now this section is because most other implementations ignore zero-keys. Now this section is because most other implementations ignore zero-keys.
>>> c.get("") >>> c.get("")
>>> c.set("", "hi") >>> c.set("", "hi")
@ -36,7 +26,7 @@ Multi functionality.
>>> c.get_multi("abc").keys() == ["a", "c", "b"] >>> c.get_multi("abc").keys() == ["a", "c", "b"]
True True
>>> c.delete_multi("abc") >>> c.delete_multi("abc")
True []
>>> c.get_multi("abc").keys() == [] >>> c.get_multi("abc").keys() == []
True True
>>> c.set_multi(dict(zip("abc", "def")), key_prefix="test_") >>> c.set_multi(dict(zip("abc", "def")), key_prefix="test_")
@ -46,7 +36,7 @@ True
>>> c.get("test_a") >>> c.get("test_a")
'd' 'd'
>>> c.delete_multi("abc", key_prefix="test_") >>> c.delete_multi("abc", key_prefix="test_")
True []
>>> bool(c.get_multi("abc", key_prefix="test_")) >>> bool(c.get_multi("abc", key_prefix="test_"))
False False
@ -54,13 +44,9 @@ Zero-key-test-time!
>>> c.get_multi([""]) >>> c.get_multi([""])
{} {}
>>> c.delete_multi([""]) >>> c.delete_multi([""])
False ['']
>>> c.set_multi({"": "hi"}) >>> c.set_multi({"": "hi"})
[''] ['']
>>> c.delete_multi({"a": "b"})
Traceback (most recent call last):
...
TypeError: keys must be a sequence, not a mapping
Timed stuff. The reason we, at UNIX times, set it two seconds in the future and Timed stuff. The reason we, at UNIX times, set it two seconds in the future and
then sleep for >3 is that memcached might round the time up and down and left then sleep for >3 is that memcached might round the time up and down and left
@ -70,36 +56,21 @@ and yeah, so you know...
True True
>>> c.get("hi") >>> c.get("hi")
'steven' 'steven'
>>> sleep(2.1) >>> sleep(1.1)
>>> c.get("hi") >>> c.get("hi")
>>> c.set("hi", "loretta", int(time()) + 2) >>> c.set("hi", "loretta", int(time()) + 2)
True True
>>> c.set_multi({"hi2": "charlotta"}, 1)
[]
>>> c.get("hi") >>> c.get("hi")
'loretta' 'loretta'
>>> c.get("hi2")
'charlotta'
>>> sleep(3.1) >>> sleep(3.1)
>>> c.get("hi"), c.get("hi2") >>> c.get("hi")
(None, None) >>>
See issue #1 ``http://github.com/lericson/pylibmc/issues/#issue/1`` -- delete
should not accept a time argument.
>>> c.delete("foo", 123)
Traceback (most recent call last):
...
TypeError: delete() takes exactly 1 argument (2 given)
>>> c.delete_multi(["foo"], time=123)
Traceback (most recent call last):
...
TypeError: 'time' is an invalid keyword argument for this function
Now for keys with funny types. Now for keys with funny types.
>>> c.set(1, "hi") >>> c.set(1, "hi")
Traceback (most recent call last): Traceback (most recent call last):
... ...
TypeError: argument 1 must be string, not int TypeError: argument 1 must be string or read-only buffer, not int
>>> c.get(1) >>> c.get(1)
Traceback (most recent call last): Traceback (most recent call last):
... ...
@ -113,23 +84,6 @@ Traceback (most recent call last):
... ...
TypeError: key must be an instance of str TypeError: key must be an instance of str
This didn't use to work, but now it does.
>>> c.get_multi([])
{}
Getting stats is fun!
>>> for (svr, stats) in c.get_stats():
... print svr
... ks = stats.keys()
... while ks:
... cks, ks = ks[:6], ks[6:]
... print ", ".join(cks)
127.0.0.1:11211 (0)
pid, total_items, uptime, version, limit_maxbytes, rusage_user
bytes_read, rusage_system, cmd_get, curr_connections, threads, total_connections
cmd_set, curr_items, get_misses, evictions, bytes, connection_structures
bytes_written, time, pointer_size, get_hits
Also test some flush all. Also test some flush all.
>>> c.set("hi", "guys") >>> c.set("hi", "guys")
True True
@ -151,235 +105,25 @@ True
False False
>>> c.delete("greta") >>> c.delete("greta")
True True
Complex data types!
>>> bla = Foo()
>>> bla.bar = "Hello!"
>>> c.set("tengil", bla)
True
>>> c.get("tengil").bar == bla.bar
True
Cloning (ethically, I don't know about it.)
>>> c is not c.clone()
True
>>> c2 = c.clone()
>>> c.set("test", "hello")
True
>>> c2.get("test")
'hello'
>>> c2.delete("test")
True
>>> del c2
Per-error exceptions
>>> c.incr("test")
Traceback (most recent call last):
...
NotFound: error 16 from memcached_increment: NOT FOUND
>>> c.incr(chr(0))
Traceback (most recent call last):
...
ProtocolError: error 8 from memcached_increment: PROTOCOL ERROR
Behaviors.
>>> c.set_behaviors({"tcp_nodelay": True, "hash": 6})
>>> list(sorted((k, v) for (k, v) in c.get_behaviors().items()
... if k in ("tcp_nodelay", "hash")))
[('hash', 6), ('tcp_nodelay', 1)]
Binary protocol!
>>> c = _pylibmc.client([test_server], binary=True)
>>> c.set("hello", "world")
True
>>> c.get("hello")
'world'
>>> c.delete("hello")
True
incr_multi
>>> c.add_multi({'a': 1, 'b': 0, 'c': 4})
[]
>>> c.incr_multi(('a', 'b', 'c'), delta=1)
>>> list(sorted(c.get_multi(('a', 'b', 'c')).items()))
[('a', 2), ('b', 1), ('c', 5)]
>>> c.delete_multi(('a', 'b', 'c'))
True
>>> c.add_multi({'a': 1, 'b': 0, 'c': 4}, key_prefix='x')
[]
>>> c.incr_multi(('a', 'b', 'c'), key_prefix='x', delta=5)
>>> list(sorted(c.get_multi(('a', 'b', 'c'), key_prefix='x').items()))
[('a', 6), ('b', 5), ('c', 9)]
>>> c.delete_multi(('a', 'b', 'c'), key_prefix='x')
True
>>> c.add('a', 1)
True
>>> c.incr_multi(('a', 'b', 'c'), key_prefix='x', delta=1)
Traceback (most recent call last):
...
NotFound: error 16 from memcached_increment: NOT FOUND
>>> c.delete('xa')
False
Empty server lists are bad for your health.
>>> c = _pylibmc.client([])
Traceback (most recent call last):
...
MemcachedError: empty server list
Python-wrapped behaviors dict
>>> pc = pylibmc.Client(["%s:%d" % test_server[1:]])
>>> (pc.behaviors["hash"], pc.behaviors["distribution"])
('default', 'modula')
>>> pc.behaviors.update({"hash": "fnv1a_32", "distribution": "consistent"})
>>> (pc.behaviors["hash"], pc.behaviors["distribution"])
('fnv1a_32', 'consistent')
>>> pc = pylibmc.Client(["%s:%d" % test_server[1:]])
>>> b = pc.behaviors
>>> ks = list(sorted(k for k in b.keys() if not k.startswith("_")))
>>> ks # doctest: +NORMALIZE_WHITESPACE
['buffer_requests', 'cache_lookups', 'cas', 'connect_timeout', 'distribution',
'failure_limit', 'hash', 'ketama', 'ketama_hash', 'ketama_weighted',
'no_block', 'receive_timeout', 'send_timeout', 'tcp_nodelay', 'verify_keys']
>>> b["hash"]
'default'
>>> b["hash"] = 'fnv1a_32'
>>> pc.behaviors["hash"]
'fnv1a_32'
>>> super(pylibmc.Client, pc).get_behaviors()["hash"]
6
Ormod's Zero-byte Adventure Story
>>> bc = _pylibmc.client([test_server], binary=True)
>>> bc.set("\0\0", "ORMOD")
True
>>> bc.get_multi(["\0\0"])
{'\x00\x00': 'ORMOD'}
Test server/client max length
>>> mc.get('x'*250)
>>> mc.get('x'*251)
Traceback (most recent call last):
...
ValueError: key too long, max is 250
Test CAS
>>> mc.behaviors['cas'] = True
>>> mc.delete('foo') and False
False
>>> mc.gets('foo')
(None, None)
>>> mc.set('foo', 'bar')
True
>>> foostr, cas = mc.gets('foo')
>>> foostr
'bar'
>>> mc.cas('foo', 'quux', cas+1)
False
>>> mc.cas('foo', 'baz', cas)
True
>>> mc.get('foo')
'baz'
>>> mc.behaviors['cas'] = False
>>> mc.gets('foo')
Traceback (most recent call last):
...
ValueError: gets without cas behavior
>>> mc.cas('foo', 'bar', 1)
Traceback (most recent call last):
...
ValueError: cas without cas behavior
""" """
# Used to test pickling. # Fix up sys.path so as to include the build/lib.*/ directory.
class Foo(object): pass
# Fix up sys.path so as to include the correct build/lib.*/ directory.
import sys import sys
from distutils.dist import Distribution import os
from distutils.command.build import build from glob import glob
build_cmd = build(Distribution({"ext_modules": True})) dist_dir = os.path.dirname(__file__)
build_cmd.finalize_options() for build_dir in glob(os.path.join(dist_dir, "build", "lib.*")):
lib_dirn = build_cmd.build_lib sys.path.insert(0, build_dir)
sys.path.insert(0, lib_dirn) break
else:
print >>sys.stderr, "Using system-wide installation of pylibmc!"
print >>sys.stderr, "==========================================\n"
import pylibmc, _pylibmc import _pylibmc
import socket import socket
__doc__ = pylibmc.__doc__ + "\n\n" + __doc__ test_server = (_pylibmc.server_type_tcp, "localhost", 11211)
# {{{ Ported cmemcache tests
import unittest
class TestCmemcached(unittest.TestCase):
def setUp(self):
self.mc = pylibmc.Client(["%s:%d" % (test_server[1:])])
def testSetAndGet(self):
self.mc.set("num12345", 12345)
self.assertEqual(self.mc.get("num12345"), 12345)
self.mc.set("str12345", "12345")
self.assertEqual(self.mc.get("str12345"), "12345")
def testDelete(self):
self.mc.set("str12345", "12345")
#delete return True on success, otherwise False
ret = self.mc.delete("str12345")
self.assertEqual(self.mc.get("str12345"), None)
self.assertEqual(ret, True)
# This test only works with old memcacheds. This has become a "client
# error" in memcached.
try:
ret = self.mc.delete("hello world")
except pylibmc.ClientError:
pass
else:
self.assertEqual(ret, False)
def testGetMulti(self):
self.mc.set("a", "valueA")
self.mc.set("b", "valueB")
self.mc.set("c", "valueC")
result = self.mc.get_multi(["a", "b", "c", "", "hello world"])
self.assertEqual(result, {'a':'valueA', 'b':'valueB', 'c':'valueC'})
def testBigGetMulti(self):
count = 10
keys = ['key%d' % i for i in xrange(count)]
pairs = zip(keys, ['value%d' % i for i in xrange(count)])
for key, value in pairs:
self.mc.set(key, value)
result = self.mc.get_multi(keys)
assert result == dict(pairs)
def testFunnyDelete(self):
result= self.mc.delete("")
self.assertEqual(result, False)
def testAppend(self):
self.mc.delete("a")
self.mc.set("a", "I ")
ret = self.mc.append("a", "Do")
result = self.mc.get("a")
self.assertEqual(result, "I Do")
def testPrepend(self):
self.mc.delete("a")
self.mc.set("a", "Do")
ret = self.mc.prepend("a", "I ")
result = self.mc.get("a")
self.assertEqual(result, "I Do")
# }}}
from os import environ
test_server = (
_pylibmc.server_type_tcp,
str(environ.get("MEMCACHED_HOST", "127.0.0.1")),
int(environ.get("MEMCACHED_PORT", "11211")))
def get_version(addr): def get_version(addr):
(type_, host, port) = addr (type_, host, port) = addr
@ -403,64 +147,13 @@ def is_alive(addr):
except (ValueError, socket.error): except (ValueError, socket.error):
return False return False
def make_full_suite():
from doctest import DocTestFinder, DocTestSuite
suite = unittest.TestSuite()
loader = unittest.TestLoader()
finder = DocTestFinder()
for modname in (__name__, "pylibmc", "_pylibmc"):
ss = (DocTestSuite(sys.modules[modname], test_finder=finder),
loader.loadTestsFromName(modname))
for subsuite in ss:
map(suite.addTest, subsuite._tests)
return suite
class _TextTestResult(unittest._TextTestResult):
def startTest(self, test):
# Treat doctests a little specially because we want each example to
# count as a test.
if hasattr(test, "_dt_test"):
self.testsRun += len(test._dt_test.examples) - 1
elif hasattr(test, "countTestCases"):
self.testsRun += test.countTestCases() - 1
return unittest._TextTestResult.startTest(self, test)
class TextTestRunner(unittest.TextTestRunner):
def _makeResult(self):
return _TextTestResult(self.stream, self.descriptions, self.verbosity)
class TestProgram(unittest.TestProgram):
defaultTest = "make_full_suite"
testRunner = TextTestRunner
def __init__(self, module="__main__", argv=None,
testLoader=unittest.defaultTestLoader):
super(TestProgram, self).__init__(
module=module, argv=argv, testLoader=testLoader,
defaultTest=self.defaultTest, testRunner=self.testRunner)
def runTests(self):
pass
def makeRunner(self):
return self.testRunner(verbosity=self.verbosity)
def run(self):
runner = self.makeRunner()
return runner.run(self.test)
if __name__ == "__main__": if __name__ == "__main__":
if hasattr(_pylibmc, "__file__"):
print "Starting tests with _pylibmc at", _pylibmc.__file__ print "Starting tests with _pylibmc at", _pylibmc.__file__
else:
print "Starting tests with static _pylibmc:", _pylibmc
print "Reported libmemcached version:", _pylibmc.libmemcached_version print "Reported libmemcached version:", _pylibmc.libmemcached_version
print "Reported pylibmc version:", _pylibmc.__version__
print "Support compression:", _pylibmc.support_compression
if not is_alive(test_server): if not is_alive(test_server):
raise SystemExit("Test server (%r) not alive." % (test_server,)) raise SystemExit("Test server (%r) not alive." % (test_server,))
import doctest
res = TestProgram().run() n_fail, n_run = doctest.testmod()
if not res.wasSuccessful(): print "Ran", n_run, "tests with", n_fail, "failures."
if n_fail:
sys.exit(1) sys.exit(1)