Compare commits
No commits in common. "master" and "1.0-alpha" have entirely different histories.
7
.gitignore
vendored
7
.gitignore
vendored
@ -1,8 +1,5 @@
|
||||
*.pyc
|
||||
*.pyo
|
||||
.DS_Store
|
||||
build
|
||||
dist
|
||||
tmp
|
||||
build/*
|
||||
dist/*
|
||||
MANIFEST
|
||||
sendapatch.se
|
||||
|
156
README.rst
156
README.rst
@ -2,7 +2,7 @@
|
||||
|
||||
__ 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.
|
||||
|
||||
__ http://tangent.org/552/libmemcached.html
|
||||
@ -11,10 +11,62 @@ __ http://tangent.org/
|
||||
`libmemcached` must be installed separately, and be available to the compiler
|
||||
and linker.
|
||||
|
||||
For installation instructions, usage notes and reference documentation, see
|
||||
pylibmc__'s home at http://sendapatch.se/projects/pylibmc/.
|
||||
Basic usage is that of `python-memcached`__, like so::
|
||||
|
||||
__ 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
|
||||
=============================
|
||||
@ -26,23 +78,6 @@ Why use `pylibmc`? Because it's fast.
|
||||
__ 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
|
||||
===
|
||||
|
||||
@ -51,76 +86,55 @@ IRC
|
||||
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``.
|
||||
- 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!
|
||||
- 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
|
||||
------------------
|
||||
|
||||
- Restructured some of the code, which should yield better performance (if not
|
||||
for that, it reads better.)
|
||||
- Fixed some memory leaks.
|
||||
- Integrated changes from `amix.dk`, which should make pylibmc work under
|
||||
Snow Leopard.
|
||||
- Add support for the boolean datatype.
|
||||
- Improved test-runner -- now tests ``build/lib.*/_pylibmc.so`` if available,
|
||||
and reports some version information.
|
||||
- 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.
|
||||
- Restructured some of the code, which should yield better performance (if not
|
||||
for that, it reads better.)
|
||||
- Fixed some memory leaks.
|
||||
- Integrated changes from `amix.dk`, which should make pylibmc work under
|
||||
Snow Leopard.
|
||||
- Add support for the boolean datatype.
|
||||
- Improved test-runner -- now tests ``build/lib.*/_pylibmc.so`` if available,
|
||||
and reports some version information.
|
||||
- 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
|
||||
------------------
|
||||
|
||||
- Added compatibility with `libmemcached` 0.26, WRT error return codes.
|
||||
- Added `flush_all` and `disconnect_all` methods.
|
||||
- Now using the latest pickling protocol.
|
||||
- Added compatibility with `libmemcached` 0.26, WRT error return codes.
|
||||
- Added `flush_all` and `disconnect_all` methods.
|
||||
- Now using the latest pickling protocol.
|
||||
|
||||
New in version 0.5
|
||||
------------------
|
||||
|
||||
- Fixed lots of memory leaks, and added support for `libmemcached` 0.23.
|
||||
- Also made the code tighter in terms of compiler pedantics.
|
||||
- Fixed lots of memory leaks, and added support for `libmemcached` 0.23.
|
||||
- Also made the code tighter in terms of compiler pedantics.
|
||||
|
||||
New in version 0.4
|
||||
------------------
|
||||
|
||||
- Renamed the C module to `_pylibmc`, and added lots of `libmemcached` constants
|
||||
to it, as well as implemented behaviors.
|
||||
- Renamed the C module to `_pylibmc`, and added lots of `libmemcached` constants
|
||||
to it, as well as implemented behaviors.
|
||||
|
1407
_pylibmcmodule.c
1407
_pylibmcmodule.c
File diff suppressed because it is too large
Load Diff
@ -68,52 +68,11 @@ typedef ssize_t Py_ssize_t;
|
||||
#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 *,
|
||||
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 */
|
||||
static PyObject *PylibMCExc_MemcachedError;
|
||||
@ -238,20 +197,16 @@ static PylibMC_Client *PylibMC_ClientType_new(PyTypeObject *, PyObject *,
|
||||
static void PylibMC_ClientType_dealloc(PylibMC_Client *);
|
||||
static int PylibMC_Client_init(PylibMC_Client *, PyObject *, PyObject *);
|
||||
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_replace(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_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_incr(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_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_get_behaviors(PylibMC_Client *);
|
||||
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 int _PylibMC_CheckKey(PyObject *);
|
||||
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 */
|
||||
static PyMethodDef PylibMC_ClientType_methods[] = {
|
||||
{"get", (PyCFunction)PylibMC_Client_get, METH_O,
|
||||
"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 a key unconditionally."},
|
||||
{"replace", (PyCFunction)PylibMC_Client_replace, METH_VARARGS|METH_KEYWORDS,
|
||||
@ -301,22 +236,16 @@ static PyMethodDef PylibMC_ClientType_methods[] = {
|
||||
"Prepend data to a key."},
|
||||
{"append", (PyCFunction)PylibMC_Client_append, METH_VARARGS|METH_KEYWORDS,
|
||||
"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 a key."},
|
||||
{"incr", (PyCFunction)PylibMC_Client_incr, METH_VARARGS,
|
||||
"Increment a key by a delta."},
|
||||
{"decr", (PyCFunction)PylibMC_Client_decr, METH_VARARGS,
|
||||
"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,
|
||||
METH_VARARGS|METH_KEYWORDS, "Get multiple keys at once."},
|
||||
{"set_multi", (PyCFunction)PylibMC_Client_set_multi,
|
||||
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,
|
||||
METH_VARARGS|METH_KEYWORDS, "Delete multiple keys at once."},
|
||||
{"get_behaviors", (PyCFunction)PylibMC_Client_get_behaviors, METH_NOARGS,
|
||||
|
@ -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>
|
@ -1 +1 @@
|
||||
#define PYLIBMC_VERSION "1.1.1"
|
||||
#define PYLIBMC_VERSION "1.0-alpha"
|
||||
|
222
pylibmc.py
Normal file
222
pylibmc.py
Normal 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()
|
@ -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"]
|
@ -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()
|
@ -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
|
@ -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())
|
@ -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
|
12
setup.py
12
setup.py
@ -15,14 +15,6 @@ defs = []
|
||||
incdirs = []
|
||||
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
|
||||
unprocessed = []
|
||||
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]
|
||||
|
||||
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",
|
||||
license="3-clause BSD <http://www.opensource.org/licenses/bsd-license.php>",
|
||||
description="Quick and small memcached client for Python",
|
||||
long_description=readme_text,
|
||||
ext_modules=[pylibmc_ext], packages=["pylibmc"])
|
||||
ext_modules=[pylibmc_ext], py_modules=["pylibmc"])
|
||||
|
137
tests.py
137
tests.py
@ -1,4 +1,4 @@
|
||||
r"""Tests. They want YOU!!
|
||||
"""Tests. They want YOU!!
|
||||
|
||||
|
||||
Basic functionality.
|
||||
@ -57,10 +57,6 @@ Zero-key-test-time!
|
||||
False
|
||||
>>> 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
|
||||
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.set("hi", "loretta", int(time()) + 2)
|
||||
True
|
||||
>>> c.set_multi({"hi2": "charlotta"}, 1)
|
||||
[]
|
||||
>>> c.get("hi")
|
||||
'loretta'
|
||||
>>> c.get("hi2")
|
||||
'charlotta'
|
||||
>>> sleep(3.1)
|
||||
>>> 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
|
||||
>>> c.get("hi")
|
||||
>>>
|
||||
|
||||
Now for keys with funny types.
|
||||
>>> c.set(1, "hi")
|
||||
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)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
@ -113,10 +94,6 @@ Traceback (most recent call last):
|
||||
...
|
||||
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
|
||||
@ -124,7 +101,7 @@ Getting stats is fun!
|
||||
... while ks:
|
||||
... cks, ks = ks[:6], ks[6:]
|
||||
... print ", ".join(cks)
|
||||
127.0.0.1:11211 (0)
|
||||
localhost: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
|
||||
@ -197,30 +174,6 @@ True
|
||||
>>> 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):
|
||||
@ -234,76 +187,23 @@ Python-wrapped behaviors dict
|
||||
>>> 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.
|
||||
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
|
||||
from distutils.dist import Distribution
|
||||
from distutils.command.build import build
|
||||
import os
|
||||
from glob import glob
|
||||
|
||||
build_cmd = build(Distribution({"ext_modules": True}))
|
||||
build_cmd.finalize_options()
|
||||
lib_dirn = build_cmd.build_lib
|
||||
sys.path.insert(0, lib_dirn)
|
||||
dist_dir = os.path.dirname(__file__)
|
||||
for build_dir in glob(os.path.join(dist_dir, "build", "lib.*")):
|
||||
sys.path.insert(0, build_dir)
|
||||
break
|
||||
else:
|
||||
print >>sys.stderr, "Using system-wide installation of pylibmc!"
|
||||
print >>sys.stderr, "==========================================\n"
|
||||
|
||||
import pylibmc, _pylibmc
|
||||
import socket
|
||||
@ -374,12 +274,7 @@ class TestCmemcached(unittest.TestCase):
|
||||
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")))
|
||||
test_server = (_pylibmc.server_type_tcp, "localhost", 11211)
|
||||
|
||||
def get_version(addr):
|
||||
(type_, host, port) = addr
|
||||
|
Reference in New Issue
Block a user