Compare commits

..

No commits in common. "master" and "1.0-alpha" have entirely different histories.

14 changed files with 663 additions and 1717 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

@ -2,7 +2,7 @@
__ http://memcached.org/ __ http://memcached.org/
It builds on the famous `libmemcached`__ C client from TangentOrg__, notable for It builds on the famous `libmemcached`__ C client from TangetOrg__, notable for
its speed and flexibility. its speed and flexibility.
__ http://tangent.org/552/libmemcached.html __ http://tangent.org/552/libmemcached.html
@ -11,10 +11,62 @@ __ 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.
For installation instructions, usage notes and reference documentation, see Basic usage is that of `python-memcached`__, like so::
pylibmc__'s home at http://sendapatch.se/projects/pylibmc/.
__ http://sendapatch.se/projects/pylibmc/ >>> import 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
============================= =============================
@ -26,23 +78,6 @@ 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
=== ===
@ -51,27 +86,6 @@ 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
------------------ ------------------

File diff suppressed because it is too large Load Diff

View File

@ -68,52 +68,11 @@ 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;
@ -238,20 +197,16 @@ 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 *);
@ -265,32 +220,12 @@ 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,22 +236,16 @@ 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,

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

222
pylibmc.py Normal file
View File

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

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

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

@ -15,14 +15,6 @@ 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:]:
@ -69,9 +61,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://sendapatch.se/projects/pylibmc/", 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="Quick and small memcached client for Python",
long_description=readme_text, long_description=readme_text,
ext_modules=[pylibmc_ext], packages=["pylibmc"]) ext_modules=[pylibmc_ext], py_modules=["pylibmc"])

137
tests.py
View File

@ -1,4 +1,4 @@
r"""Tests. They want YOU!! """Tests. They want YOU!!
Basic functionality. Basic functionality.
@ -57,10 +57,6 @@ 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
@ -74,32 +70,17 @@ 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("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,10 +94,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! Getting stats is fun!
>>> for (svr, stats) in c.get_stats(): >>> for (svr, stats) in c.get_stats():
... print svr ... print svr
@ -124,7 +101,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)
127.0.0.1:11211 (0) localhost: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
@ -197,30 +174,6 @@ 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):
@ -234,76 +187,23 @@ 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 correct build/lib.*/ directory. # Fix up sys.path so as to include the 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, _pylibmc
import socket import socket
@ -374,12 +274,7 @@ class TestCmemcached(unittest.TestCase):
self.assertEqual(result, "I Do") self.assertEqual(result, "I Do")
# }}} # }}}
from os import environ test_server = (_pylibmc.server_type_tcp, "localhost", 11211)
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