Compare commits

..

70 Commits

Author SHA1 Message Date
f7bae96d11 reorder vars 2010-11-23 19:54:22 -05:00
b201a84ba1 making the custom_flag bit customizable instead of fixed 2010-11-22 20:05:37 -05:00
5549b58553 define a use_custom_flag option which will add the PYLIBMC_FLAG_CUSTOM define to flags if true 2010-11-22 16:42:41 -05:00
lericson
8d50d77001 Protect Deflate with GIL acquiring
ddd2f011 removed this.
2010-10-10 15:17:08 +02:00
John Watson
7245d36553 Fix RunSetCommandMulti using time for min_compress when calling
RunSetCommand
2010-10-09 11:38:02 -07:00
John Watson
c6cb0db9bc Merge branch 'master' of git://github.com/lericson/pylibmc 2010-10-09 11:31:25 -07:00
lericson
e3a6828569 Better handle return value in gets 2010-10-09 01:13:10 +02:00
lericson
52c00509cd Remove paranoid memcached_quit call 2010-10-09 00:54:07 +02:00
lericson
8186f9f009 Always quit and free memcached result in gets 2010-10-09 00:53:37 +02:00
lericson
9d4b01ff4d Turn pylibmc into a shallow package 2010-10-08 22:03:39 +02:00
lericson
a3605ca55f Always return a two-tuple in client.gets 2010-10-08 22:02:41 +02:00
lericson
432cd63ccd Nitpick program flow of client.gets and others 2010-10-08 22:02:36 +02:00
lericson
3f687bb551 Instigate The List of Honored Men 2010-10-08 22:02:25 +02:00
ketralnis
55174bf9b9 Fix a string length related bug resulting in integers sometimes not being parsed correctly 2010-10-04 21:42:02 -07:00
ketralnis
8ca61197da Add tests for CAS 2010-09-28 20:34:16 -07:00
ketralnis
0c49d98fb4 Add CAS support (client.gets, client.cas). Note that this is not
compatible with python-memcached's implementation
2010-09-28 20:27:53 -07:00
John Watson
ddd2f011f7 Remove use of the Simplified GIL State API so pylibmc works in sub-interpreters
from commit 74d5f9ac0b9da2643c3f
2010-09-10 14:53:27 -07:00
Johan Bergström
725134931b Fix off-by-one in key length check
Fixes #18
2010-08-30 13:41:34 +02:00
Noah Silas
314562c437 Override clone on Client so that you can call str() on client instances from a ThreadMappedPool 2010-06-22 00:17:18 +08:00
lericson
3cb953aee9 Add changelog for 1.1.1 2010-06-07 16:06:00 +02:00
lericson
d642a792d2 Prepare pylibmc 1.1.1 release 2010-06-07 15:23:41 +02:00
lericson
2bbccb6a36 Add future import so Python 2.5 can handle doctests 2010-06-07 15:23:15 +02:00
lericson
b2cc46a67a Add --binary argument 2010-06-04 19:03:02 +02:00
lericson
1336dc164d Improve string representations 2010-06-04 19:02:25 +02:00
lericson
190a548ab3 Prepare release of pylibmc 1.1 2010-06-03 21:13:46 +02:00
lericson
91c7b3fd56 Fix libmemcached 0.32 support 2010-06-03 21:13:45 +02:00
lericson
25396a64bd Use proper indent level for lists in README 2010-06-03 16:42:59 +02:00
lericson
f357c00f2b Remove deprecated space-based behavior names 2010-06-03 16:36:21 +02:00
lericson
74d5f9ac0b Properly manage GIL in RunSetCommand 2010-05-27 21:54:51 +02:00
lericson
86f6256d3a Fix backward compatibility for set_multi timeout.
Fixes #13 HOH
2010-05-27 21:54:24 +02:00
lericson
02c7681ff2 Handle NUL-byte keys in get_multi
Thanks to Ormod for finding, reporting and drilling down the bug.
2010-05-27 14:26:24 +02:00
lericson
212a69e09f Fix valgrind warnings 2010-05-12 01:20:20 +02:00
lericson
21b49defee Sometime in the future, maybe... 2010-05-12 01:20:05 +02:00
lericson
374ceed11f Simplify get_multi control flow and syntax nits 2010-05-12 01:19:55 +02:00
lericson
66f4bcfc41 Second pass of syntax nitpicks 2010-05-12 01:18:29 +02:00
lericson
2e83a68ed9 Test 127.0.0.1 rather than localhost (IPv6) 2010-05-12 01:15:12 +02:00
lericson
b4a1e6ab6b Support new-style stats & server iteration 2010-05-11 12:32:07 +02:00
lericson
53879eb8a9 First pass of syntax & indent nitpick 2010-05-11 11:47:14 +02:00
lericson
e8593ba7c7 Always terminate Py_*_ALLOW_THREADS with semicolon 2010-05-10 13:52:51 +02:00
lericson
2f09c230a3 Fix what appears to be an obvious test failure 2010-05-10 13:24:39 +02:00
lericson
76d422cc0f Fix what seems to be merge-related issues 2010-05-10 13:24:07 +02:00
lericson
2ff9e471c2 Zero out mset memory in one fell swoop 2010-05-10 13:23:20 +02:00
ketralnis
eb21e83e2c Acquire/release the GIL only once for set_multi operations, and add add_multi/incr_multi operations 2010-05-10 13:11:13 +02:00
ketralnis
4674638327 A second pass at releasing the GIL during blocking operations: only release/reaquire once for a get_multi operation 2010-05-10 13:11:10 +02:00
lericson
6687cc0464 Add some function names to arg parsing lines 2010-05-10 12:44:15 +02:00
lericson
f88c8c3afe Stop accepting time argument for delete. Fixes #1 2010-05-10 12:42:46 +02:00
lericson
5c059a0ccd Use snprintf to avoid nonsensical Clang warnings 2010-05-10 12:20:14 +02:00
lericson
2b39ddf9ec Add a more useful command-line interface 2010-04-22 00:08:03 +02:00
lericson
fcfe08f3c3 Merge remote branch 'ketralnis/master' 2010-03-27 20:07:55 +01:00
ketralnis
84875a0c6a First pass at freeing the GIL during blocking calls 2010-03-22 21:15:38 -07:00
lericson
0987c37160 Prepare 1.0 release 2010-03-22 22:33:26 +01:00
lericson
48e131b071 Nitpick PyPI documentation wording 2010-03-22 22:33:17 +01:00
lericson
ee47d6ab7b Nitpick syntax to go below 80 chars 2010-03-22 22:25:09 +01:00
lericson
92c1fbb585 Fix up docstrings for pylibmc module 2010-03-22 21:52:09 +01:00
lericson
61932d50b9 Check for minor version requirement 2010-03-22 21:06:37 +01:00
lericson
3702d07434 Add changelog for 1.0 2010-03-21 22:44:04 +01:00
lericson
87e7c15064 Prepare 1.0-beta release. 2010-03-19 20:12:28 +01:00
lericson
956355ee27 Expand pylibmc.ClientPool.reserve documentation. 2010-03-16 23:44:18 +01:00
lericson
b3b36b3acb Make pylibmc.ClientPool signature backwards-compatible. 2010-03-16 23:43:54 +01:00
lericson
22f0cd6663 Merge remote branch 'ketralnis/master' 2010-03-16 23:31:40 +01:00
ketralnis
0e383d7289 Change ClientPool to properly block when a client isn't available 2010-03-16 15:21:59 -07:00
lericson
a7b0c6e5da Allow using environment variables for package dirs 2010-03-16 23:06:18 +01:00
lericson
c2b8573adf Fix up .gitignore 2010-03-16 23:00:50 +01:00
lericson
3331c59d77 Elusive is the one missing DECREF, conclusive is this commit.
Fixes #6 - the issue was that when PyTuple_Pack constructs a tuple, it
makes a reference of its own. This was not taken into account, so a
DECREF was missing.
2010-03-16 19:59:17 +01:00
lericson
c3215866c5 Fix get_multi with an 0-sequence.
Used to give weird errors like NULL return w/o exception etc. Fixes #7.
2010-02-22 16:36:56 +01:00
lericson
8a1e5c752d Use distutils to generate path to pylibmc in tests. 2010-01-27 13:20:45 +01:00
lericson
aff42d767b Check for mappings in delete_multi and err out.
Previously, this would cause internal trickery to go wrong and call an
inner function with too many arguments, then fail. The error wouldn't
bubble up either, and a SEGV would ensue.
2010-01-27 11:41:07 +01:00
lericson
587df7121c Add notes on installation options. 2010-01-18 15:19:46 +01:00
lericson
59025f48db Make documentation reference actual location.
See http://sendapatch.se/projects/pylibmc/.
2010-01-03 19:20:47 +01:00
lericson
9b4f66952c Typofix. 2009-12-30 18:39:37 +01:00
14 changed files with 1721 additions and 667 deletions

7
.gitignore vendored
View File

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

View File

@ -2,7 +2,7 @@
__ http://memcached.org/ __ http://memcached.org/
It builds on the famous `libmemcached`__ C client from TangetOrg__, notable for It builds on the famous `libmemcached`__ C client from TangentOrg__, notable for
its speed and flexibility. its speed and flexibility.
__ http://tangent.org/552/libmemcached.html __ http://tangent.org/552/libmemcached.html
@ -11,62 +11,10 @@ __ http://tangent.org/
`libmemcached` must be installed separately, and be available to the compiler `libmemcached` must be installed separately, and be available to the compiler
and linker. and linker.
Basic usage is that of `python-memcached`__, like so:: For installation instructions, usage notes and reference documentation, see
pylibmc__'s home at http://sendapatch.se/projects/pylibmc/.
>>> import pylibmc __ http://sendapatch.se/projects/pylibmc/
>>> mc = pylibmc.Client(["127.0.0.1:11211"])
>>> mc.set("foo", "Hello world!")
True
>>> mc.get("foo")
'Hello world!'
__ http://www.tummy.com/Community/software/python-memcached/
There's also support for some other features not present in other Python
libraries, like the binary protocol::
>>> mc = pylibmc.Client(["127.0.0.1"], binary=True)
Behaviors
=========
`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``.
Pooling
=======
In multithreaded environments, accessing the same memcached client object is
both unsafe and counter-productive in terms of performance. `libmemcached`'s
take on this is to introduce pooling on C level, which is correspondingly
mapped to pooling on Python level in `pylibmc`::
>>> mc = pylibmc.Client(["127.0.0.1"])
>>> pool = pylibmc.ThreadMappedPool(mc)
>>> # (in a thread...)
>>> with pool.reserve() as mc:
... mc.set("hello", "world")
For more information on pooling, see `my two`__ `long posts`__ about it.
__ 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
Comparison to other libraries Comparison to other libraries
============================= =============================
@ -78,6 +26,23 @@ Why use `pylibmc`? Because it's fast.
__ 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 __ 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
=== ===
@ -86,55 +51,76 @@ IRC
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 New in version 0.9
------------------ ------------------
- Added a ``get_stats`` method, which behaves exactly like - Added a ``get_stats`` method, which behaves exactly like
`python-memcached`'s equivalent. `python-memcached`'s equivalent.
- Gives the empty string for empty memcached values like `python-memcached` - Gives the empty string for empty memcached values like `python-memcached`
does. does.
- Added exceptions for most `libmemcached` return codes. - Added exceptions for most `libmemcached` return codes.
- Fixed an issue with ``Client.behaviors.update``. - Fixed an issue with ``Client.behaviors.update``.
New in version 0.8 New in version 0.8
------------------ ------------------
- Pooling helpers are now available. See ``pooling.rst`` in the distribution. - Pooling helpers are now available. See ``pooling.rst`` in the distribution.
- The binary protocol is now properly exposed, simply pass ``binary=True`` to - The binary protocol is now properly exposed, simply pass ``binary=True`` to
the constructor and there you go. the constructor and there you go.
- Call signatures now match `libmemcached` 0.32, but should work with older - Call signatures now match `libmemcached` 0.32, but should work with older
versions. Remember to run the tests! 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. - Builds with Python 2.4, tests run fine, but not officially supported.
- Fixed critical bugs in behavior manipulation. - 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.

File diff suppressed because it is too large Load Diff

View File

@ -68,11 +68,52 @@ typedef ssize_t Py_ssize_t;
#define PYLIBMC_FLAG_ZLIB (1 << 3) #define PYLIBMC_FLAG_ZLIB (1 << 3)
/* }}} */ /* }}} */
#define PYLIBMC_INC (1 << 0)
#define PYLIBMC_DEC (1 << 1)
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;
@ -197,16 +238,20 @@ 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 *);
@ -220,12 +265,32 @@ 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,
@ -236,16 +301,22 @@ 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,

8
coders.rst Normal file
View File

@ -0,0 +1,8 @@
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 +1 @@
#define PYLIBMC_VERSION "1.0-alpha" #define PYLIBMC_VERSION "1.1.1"

View File

@ -1,222 +0,0 @@
"""`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!
>>> import pylibmc
>>> mc = pylibmc.Client(["127.0.0.1"])
>>> b = mc.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'
>>> mc.behaviors["hash"]
'fnv1a_32'
>>> super(pylibmc.Client, mc).get_behaviors()["hash"]
6
"""
import _pylibmc
from warnings import warn
__all__ = ["hashers", "distributions", "Client"]
__version__ = _pylibmc.__version__
support_compression = _pylibmc.support_compression
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 very module object currently constructed.
import sys
modself = sys.modules[__name__]
for name, exc in _pylibmc.exceptions:
setattr(modself, 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())
class Client(_pylibmc.client):
def __init__(self, servers, binary=False):
"""Initialize a memcached client instance.
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
prefixed with `udp:`, a UDP connection.
If *binary* is True, the binary memcached protocol is used.
"""
addr_tups = []
for server in servers:
addr = server
port = 11211
if server.startswith("udp:"):
stype = _pylibmc.server_type_udp
addr = addr[4:]
if ":" in server:
(addr, port) = addr.split(":", 1)
port = int(port)
elif ":" in server:
stype = _pylibmc.server_type_tcp
(addr, port) = server.split(":", 1)
port = int(port)
elif "/" in server:
stype = _pylibmc.server_type_unix
port = 0
else:
stype = _pylibmc.server_type_tcp
addr_tups.append((stype, addr, port))
super(Client, self).__init__(servers=addr_tups, binary=binary)
def get_behaviors(self):
"""Gets the behaviors from the underlying C client instance.
Reverses the integer constants for `hash` and `distribution` into more
understandable string values. See *set_behaviors* for info.
"""
behaviors = super(Client, self).get_behaviors()
behaviors["hash"] = hashers_rvs[behaviors["hash"]]
behaviors["distribution"] = distributions_rvs[behaviors["distribution"]]
return BehaviorDict(self, behaviors)
def set_behaviors(self, behaviors):
"""Sets the behaviors on the underlying C client instance.
Takes care of morphing the `hash` key, if specified, into the
corresponding integer constant (which the C client expects.) If,
however, an unknown value is specified, it's passed on to the C client
(where it most surely will error out.)
This also happens for `distribution`.
"""
behaviors = behaviors.copy()
if any(" " in k for k in behaviors):
warn(DeprecationWarning("space-delimited behavior names "
"are deprecated"))
for k in [k for k in behaviors if " " in k]:
behaviors[k.replace(" ", "_")] = behaviors.pop(k)
if behaviors.get("hash") is not None:
behaviors["hash"] = hashers[behaviors["hash"]]
if behaviors.get("ketama_hash") is not None:
behaviors["ketama_hash"] = hashers[behaviors["ketama_hash"]]
if behaviors.get("distribution") is not None:
behaviors["distribution"] = distributions[behaviors["distribution"]]
return super(Client, self).set_behaviors(behaviors)
behaviors = property(get_behaviors, set_behaviors)
@property
def behaviours(self):
raise AttributeError("nobody uses british spellings")
from contextlib import contextmanager
class ClientPool(list):
"""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
"""
@contextmanager
def reserve(self):
"""Reserve a client, and put it back into the pool when done."""
mc = self.pop()
try:
yield mc
finally:
self.append(mc)
def fill(self, mc, n_slots):
"""Fill *n_slots* of the pool with clones of *mc*."""
for i in xrange(n_slots):
self.append(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:
del ThreadMappedPool
if __name__ == "__main__":
import doctest
doctest.testmod()

73
pylibmc/__init__.py Normal file
View File

@ -0,0 +1,73 @@
"""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"]

48
pylibmc/__main__.py Normal file
View File

@ -0,0 +1,48 @@
"""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()

90
pylibmc/client.py Normal file
View File

@ -0,0 +1,90 @@
"""Python-level wrapper client"""
import _pylibmc
from .consts import (hashers, distributions,
hashers_rvs, distributions_rvs,
BehaviorDict)
class Client(_pylibmc.client):
def __init__(self, servers, binary=False):
"""Initialize a memcached client instance.
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
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 = []
for server in servers:
addr = server
port = 11211
if server.startswith("udp:"):
stype = _pylibmc.server_type_udp
addr = addr[4:]
if ":" in server:
(addr, port) = addr.split(":", 1)
port = int(port)
elif ":" in server:
stype = _pylibmc.server_type_tcp
(addr, port) = server.split(":", 1)
port = int(port)
elif "/" in server:
stype = _pylibmc.server_type_unix
port = 0
else:
stype = _pylibmc.server_type_tcp
addr_tups.append((stype, addr, port))
super(Client, self).__init__(servers=addr_tups, binary=binary)
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):
"""Gets the behaviors from the underlying C client instance.
Reverses the integer constants for `hash` and `distribution` into more
understandable string values. See *set_behaviors* for info.
"""
bvrs = super(Client, self).get_behaviors()
bvrs["hash"] = hashers_rvs[bvrs["hash"]]
bvrs["distribution"] = distributions_rvs[bvrs["distribution"]]
return BehaviorDict(self, bvrs)
def set_behaviors(self, behaviors):
"""Sets the behaviors on the underlying C client instance.
Takes care of morphing the `hash` key, if specified, into the
corresponding integer constant (which the C client expects.) If,
however, an unknown value is specified, it's passed on to the C client
(where it most surely will error out.)
This also happens for `distribution`.
"""
behaviors = behaviors.copy()
if behaviors.get("hash") is not None:
behaviors["hash"] = hashers[behaviors["hash"]]
if behaviors.get("ketama_hash") is not None:
behaviors["ketama_hash"] = hashers[behaviors["ketama_hash"]]
if behaviors.get("distribution") is not None:
behaviors["distribution"] = distributions[behaviors["distribution"]]
return super(Client, self).set_behaviors(behaviors)
behaviors = property(get_behaviors, set_behaviors)
@property
def behaviours(self):
raise AttributeError("nobody uses british spellings")
def clone(self):
obj = super(Client, self).clone()
obj.addresses = list(self.addresses)
obj.binary = self.binary
return obj

40
pylibmc/consts.py Normal file
View File

@ -0,0 +1,40 @@
"""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())

97
pylibmc/pools.py Normal file
View File

@ -0,0 +1,97 @@
"""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

@ -15,6 +15,14 @@ defs = []
incdirs = [] incdirs = []
libdirs = [] libdirs = []
def append_env(L, e):
v = os.environ.get(e)
if v and os.path.exists(v):
L.append(v)
append_env(pkgdirs, "LIBMEMCACHED")
append_env(pkgdirs, "ZLIB")
# Hack up sys.argv, yay # Hack up sys.argv, yay
unprocessed = [] unprocessed = []
for arg in sys.argv[1:]: for arg in sys.argv[1:]:
@ -61,9 +69,9 @@ readme_text = open("README.rst", "U").read()
version = open("pylibmc-version.h", "U").read().strip().split("\"")[1] version = open("pylibmc-version.h", "U").read().strip().split("\"")[1]
setup(name="pylibmc", version=version, setup(name="pylibmc", version=version,
url="http://lericson.blogg.se/code/category/pylibmc.html", url="http://sendapatch.se/projects/pylibmc/",
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="Quick and small memcached client for Python",
long_description=readme_text, long_description=readme_text,
ext_modules=[pylibmc_ext], py_modules=["pylibmc"]) ext_modules=[pylibmc_ext], packages=["pylibmc"])

137
tests.py
View File

@ -1,4 +1,4 @@
"""Tests. They want YOU!! r"""Tests. They want YOU!!
Basic functionality. Basic functionality.
@ -57,6 +57,10 @@ Zero-key-test-time!
False 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,17 +74,32 @@ True
>>> 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("hi"), c.get("hi2")
>>> (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 or read-only buffer, not int TypeError: argument 1 must be string, not int
>>> c.get(1) >>> c.get(1)
Traceback (most recent call last): Traceback (most recent call last):
... ...
@ -94,6 +113,10 @@ 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! Getting stats is fun!
>>> for (svr, stats) in c.get_stats(): >>> for (svr, stats) in c.get_stats():
... print svr ... print svr
@ -101,7 +124,7 @@ Getting stats is fun!
... while ks: ... while ks:
... cks, ks = ks[:6], ks[6:] ... cks, ks = ks[:6], ks[6:]
... print ", ".join(cks) ... print ", ".join(cks)
localhost:11211 (0) 127.0.0.1:11211 (0)
pid, total_items, uptime, version, limit_maxbytes, rusage_user pid, total_items, uptime, version, limit_maxbytes, rusage_user
bytes_read, rusage_system, cmd_get, curr_connections, threads, total_connections bytes_read, rusage_system, cmd_get, curr_connections, threads, total_connections
cmd_set, curr_items, get_misses, evictions, bytes, connection_structures cmd_set, curr_items, get_misses, evictions, bytes, connection_structures
@ -174,6 +197,30 @@ True
>>> c.delete("hello") >>> c.delete("hello")
True 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. Empty server lists are bad for your health.
>>> c = _pylibmc.client([]) >>> c = _pylibmc.client([])
Traceback (most recent call last): Traceback (most recent call last):
@ -187,23 +234,76 @@ Python-wrapped behaviors dict
>>> pc.behaviors.update({"hash": "fnv1a_32", "distribution": "consistent"}) >>> pc.behaviors.update({"hash": "fnv1a_32", "distribution": "consistent"})
>>> (pc.behaviors["hash"], pc.behaviors["distribution"]) >>> (pc.behaviors["hash"], pc.behaviors["distribution"])
('fnv1a_32', 'consistent') ('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. # Used to test pickling.
class Foo(object): pass class Foo(object): pass
# Fix up sys.path so as to include the build/lib.*/ directory. # Fix up sys.path so as to include the correct build/lib.*/ directory.
import sys import sys
import os from distutils.dist import Distribution
from glob import glob from distutils.command.build import build
dist_dir = os.path.dirname(__file__) build_cmd = build(Distribution({"ext_modules": True}))
for build_dir in glob(os.path.join(dist_dir, "build", "lib.*")): build_cmd.finalize_options()
sys.path.insert(0, build_dir) lib_dirn = build_cmd.build_lib
break sys.path.insert(0, lib_dirn)
else:
print >>sys.stderr, "Using system-wide installation of pylibmc!"
print >>sys.stderr, "==========================================\n"
import pylibmc, _pylibmc import pylibmc, _pylibmc
import socket import socket
@ -274,7 +374,12 @@ class TestCmemcached(unittest.TestCase):
self.assertEqual(result, "I Do") self.assertEqual(result, "I Do")
# }}} # }}}
test_server = (_pylibmc.server_type_tcp, "localhost", 11211) 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