mirror of https://github.com/ledisdb/ledisdb.git
commit
1dd8720562
3
Makefile
3
Makefile
|
@ -23,3 +23,6 @@ clean:
|
||||||
|
|
||||||
test:
|
test:
|
||||||
go test -tags '$(GO_BUILD_TAGS)' ./...
|
go test -tags '$(GO_BUILD_TAGS)' ./...
|
||||||
|
|
||||||
|
pytest:
|
||||||
|
sh client/ledis-py/tests/all.sh
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
.PHONY: test
|
||||||
|
|
||||||
|
|
||||||
|
test:
|
||||||
|
sh tests/all.sh
|
|
@ -4,7 +4,7 @@ import time as mod_time
|
||||||
from itertools import chain, starmap
|
from itertools import chain, starmap
|
||||||
from ledis._compat import (b, izip, imap, iteritems,
|
from ledis._compat import (b, izip, imap, iteritems,
|
||||||
basestring, long, nativestr, bytes)
|
basestring, long, nativestr, bytes)
|
||||||
from ledis.connection import ConnectionPool, UnixDomainSocketConnection
|
from ledis.connection import ConnectionPool, UnixDomainSocketConnection, Token
|
||||||
from ledis.exceptions import (
|
from ledis.exceptions import (
|
||||||
ConnectionError,
|
ConnectionError,
|
||||||
DataError,
|
DataError,
|
||||||
|
@ -64,6 +64,31 @@ def int_or_none(response):
|
||||||
return int(response)
|
return int(response)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_info(response):
|
||||||
|
|
||||||
|
info = {}
|
||||||
|
response = nativestr(response)
|
||||||
|
|
||||||
|
def get_value(value):
|
||||||
|
if ',' not in value or '=' not in value:
|
||||||
|
try:
|
||||||
|
if '.' in value:
|
||||||
|
return float(value)
|
||||||
|
else:
|
||||||
|
return int(value)
|
||||||
|
except ValueError:
|
||||||
|
return value
|
||||||
|
|
||||||
|
for line in response.splitlines():
|
||||||
|
if line and not line.startswith('#'):
|
||||||
|
if line.find(':') != -1:
|
||||||
|
key, value = line.split(':', 1)
|
||||||
|
info[key] = get_value(value)
|
||||||
|
|
||||||
|
return info
|
||||||
|
|
||||||
|
# def parse_lscan(response, )
|
||||||
|
|
||||||
class Ledis(object):
|
class Ledis(object):
|
||||||
"""
|
"""
|
||||||
Implementation of the Redis protocol.
|
Implementation of the Redis protocol.
|
||||||
|
@ -111,7 +136,9 @@ class Ledis(object):
|
||||||
'HGETALL': lambda r: r and pairs_to_dict(r) or {},
|
'HGETALL': lambda r: r and pairs_to_dict(r) or {},
|
||||||
'PING': lambda r: nativestr(r) == 'PONG',
|
'PING': lambda r: nativestr(r) == 'PONG',
|
||||||
'SET': lambda r: r and nativestr(r) == 'OK',
|
'SET': lambda r: r and nativestr(r) == 'OK',
|
||||||
},
|
'INFO': parse_info,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -219,8 +246,15 @@ class Ledis(object):
|
||||||
db = 0
|
db = 0
|
||||||
return self.execute_command('SELECT', db)
|
return self.execute_command('SELECT', db)
|
||||||
|
|
||||||
def info(self, section):
|
def info(self, section=None):
|
||||||
return self.execute_command('PING', section)
|
"""
|
||||||
|
Return
|
||||||
|
"""
|
||||||
|
|
||||||
|
if section is None:
|
||||||
|
return self.execute_command("INFO")
|
||||||
|
else:
|
||||||
|
return self.execute_command('INFO', section)
|
||||||
|
|
||||||
def flushall(self):
|
def flushall(self):
|
||||||
return self.execute_command('FLUSHALL')
|
return self.execute_command('FLUSHALL')
|
||||||
|
@ -350,8 +384,21 @@ class Ledis(object):
|
||||||
"Removes an expiration on name"
|
"Removes an expiration on name"
|
||||||
return self.execute_command('PERSIST', name)
|
return self.execute_command('PERSIST', name)
|
||||||
|
|
||||||
def scan(self, key, match = "", count = 10):
|
def scan(self, key="" , match=None, count=10):
|
||||||
return self.execute_command("SCAN", key, match, count)
|
pieces = [key]
|
||||||
|
if match is not None:
|
||||||
|
pieces.extend(["MATCH", match])
|
||||||
|
|
||||||
|
pieces.extend(["COUNT", count])
|
||||||
|
|
||||||
|
return self.execute_command("SCAN", *pieces)
|
||||||
|
|
||||||
|
def scan_iter(self, match=None, count=10):
|
||||||
|
key = ""
|
||||||
|
while key != "":
|
||||||
|
key, data = self.scan(key=key, match=match, count=count)
|
||||||
|
for item in data:
|
||||||
|
yield item
|
||||||
|
|
||||||
#### LIST COMMANDS ####
|
#### LIST COMMANDS ####
|
||||||
def lindex(self, name, index):
|
def lindex(self, name, index):
|
||||||
|
@ -428,8 +475,8 @@ class Ledis(object):
|
||||||
"Removes an expiration on ``name``"
|
"Removes an expiration on ``name``"
|
||||||
return self.execute_command('LPERSIST', name)
|
return self.execute_command('LPERSIST', name)
|
||||||
|
|
||||||
def lscan(self, key, match = "", count = 10):
|
def lscan(self, key="", match=None, count=10):
|
||||||
return self.execute_command("LSCAN", key, match, count)
|
return self.scan_generic("LSCAN", key=key, match=match, count=count)
|
||||||
|
|
||||||
|
|
||||||
#### SET COMMANDS ####
|
#### SET COMMANDS ####
|
||||||
|
@ -528,8 +575,8 @@ class Ledis(object):
|
||||||
"Removes an expiration on name"
|
"Removes an expiration on name"
|
||||||
return self.execute_command('SPERSIST', name)
|
return self.execute_command('SPERSIST', name)
|
||||||
|
|
||||||
def sscan(self, key, match = "", count = 10):
|
def sscan(self, key="", match=None, count = 10):
|
||||||
return self.execute_command("SSCAN", key, match, count)
|
return self.scan_generic("SSCAN", key=key, match=match, count=count)
|
||||||
|
|
||||||
|
|
||||||
#### SORTED SET COMMANDS ####
|
#### SORTED SET COMMANDS ####
|
||||||
|
@ -727,9 +774,17 @@ class Ledis(object):
|
||||||
"Removes an expiration on name"
|
"Removes an expiration on name"
|
||||||
return self.execute_command('ZPERSIST', name)
|
return self.execute_command('ZPERSIST', name)
|
||||||
|
|
||||||
def zscan(self, key, match = "", count = 10):
|
|
||||||
return self.execute_command("ZSCAN", key, match, count)
|
|
||||||
|
|
||||||
|
def scan_generic(self, scan_type, key="", match=None, count=10):
|
||||||
|
pieces = [key]
|
||||||
|
if match is not None:
|
||||||
|
pieces.extend([Token("MATCH"), match])
|
||||||
|
pieces.extend([Token("count"), count])
|
||||||
|
scan_type = scan_type.upper()
|
||||||
|
return self.execute_command(scan_type, *pieces)
|
||||||
|
|
||||||
|
def zscan(self, key="", match=None, count=10):
|
||||||
|
return self.scan_generic("ZSCAN", key=key, match=match, count=count)
|
||||||
|
|
||||||
#### HASH COMMANDS ####
|
#### HASH COMMANDS ####
|
||||||
def hdel(self, name, *keys):
|
def hdel(self, name, *keys):
|
||||||
|
@ -823,8 +878,8 @@ class Ledis(object):
|
||||||
"Removes an expiration on name"
|
"Removes an expiration on name"
|
||||||
return self.execute_command('HPERSIST', name)
|
return self.execute_command('HPERSIST', name)
|
||||||
|
|
||||||
def hscan(self, key, match = "", count = 10):
|
def hscan(self, key="", match=None, count=10):
|
||||||
return self.execute_command("HSCAN", key, match, count)
|
return self.scan_generic("HSCAN", key=key, match=match, count=count)
|
||||||
|
|
||||||
|
|
||||||
### BIT COMMANDS
|
### BIT COMMANDS
|
||||||
|
@ -902,8 +957,8 @@ class Ledis(object):
|
||||||
"Removes an expiration on name"
|
"Removes an expiration on name"
|
||||||
return self.execute_command('BPERSIST', name)
|
return self.execute_command('BPERSIST', name)
|
||||||
|
|
||||||
def bscan(self, key, match = "", count = 10):
|
def bscan(self, key="", match=None, count=10):
|
||||||
return self.execute_command("BSCAN", key, match, count)
|
return self.scan_generic("BSCAN", key=key, match=match, count=count)
|
||||||
|
|
||||||
def eval(self, script, keys, *args):
|
def eval(self, script, keys, *args):
|
||||||
n = len(keys)
|
n = len(keys)
|
||||||
|
@ -924,6 +979,7 @@ class Ledis(object):
|
||||||
def scriptflush(self):
|
def scriptflush(self):
|
||||||
return self.execute_command('SCRIPT', 'FLUSH')
|
return self.execute_command('SCRIPT', 'FLUSH')
|
||||||
|
|
||||||
|
|
||||||
class Transaction(Ledis):
|
class Transaction(Ledis):
|
||||||
def __init__(self, connection_pool, response_callbacks):
|
def __init__(self, connection_pool, response_callbacks):
|
||||||
self.connection_pool = connection_pool
|
self.connection_pool = connection_pool
|
||||||
|
|
|
@ -588,3 +588,23 @@ class BlockingConnectionPool(object):
|
||||||
timeout=self.timeout,
|
timeout=self.timeout,
|
||||||
connection_class=self.connection_class,
|
connection_class=self.connection_class,
|
||||||
queue_class=self.queue_class, **self.connection_kwargs)
|
queue_class=self.queue_class, **self.connection_kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class Token(object):
|
||||||
|
"""
|
||||||
|
Literal strings in Redis commands, such as the command names and any
|
||||||
|
hard-coded arguments are wrapped in this class so we know not to apply
|
||||||
|
and encoding rules on them.
|
||||||
|
"""
|
||||||
|
def __init__(self, value):
|
||||||
|
if isinstance(value, Token):
|
||||||
|
value = value.value
|
||||||
|
self.value = value
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return self.value
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.value
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
dbs=(leveldb rocksdb hyperleveldb goleveldb boltdb lmdb)
|
||||||
|
for db in "${dbs[@]}"
|
||||||
|
do
|
||||||
|
killall ledis-server
|
||||||
|
ledis-server -db_name=$db &
|
||||||
|
py.test
|
||||||
|
done
|
|
@ -17,8 +17,7 @@ class TestCmdBit(unittest.TestCase):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
l.bdelete('a')
|
l.flushdb()
|
||||||
l.bdelete('non_exists_key')
|
|
||||||
|
|
||||||
def test_bget(self):
|
def test_bget(self):
|
||||||
"bget is the same as get in K/V commands"
|
"bget is the same as get in K/V commands"
|
||||||
|
|
|
@ -19,7 +19,7 @@ class TestCmdHash(unittest.TestCase):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
l.hmclear('myhash', 'a')
|
l.flushdb()
|
||||||
|
|
||||||
|
|
||||||
def test_hdel(self):
|
def test_hdel(self):
|
||||||
|
|
|
@ -18,7 +18,7 @@ class TestCmdKv(unittest.TestCase):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
l.delete('a', 'b', 'c', 'non_exist_key')
|
l.flushdb()
|
||||||
|
|
||||||
def test_decr(self):
|
def test_decr(self):
|
||||||
assert l.delete('a') == 1
|
assert l.delete('a') == 1
|
||||||
|
|
|
@ -18,7 +18,7 @@ class TestCmdList(unittest.TestCase):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
l.lmclear('mylist', 'mylist1', 'mylist2')
|
l.flushdb()
|
||||||
|
|
||||||
def test_lindex(self):
|
def test_lindex(self):
|
||||||
l.rpush('mylist', '1', '2', '3')
|
l.rpush('mylist', '1', '2', '3')
|
||||||
|
|
|
@ -12,14 +12,44 @@ from util import expire_at, expire_at_seconds
|
||||||
l = ledis.Ledis(port=6380)
|
l = ledis.Ledis(port=6380)
|
||||||
|
|
||||||
|
|
||||||
|
simple_script = "return {KEYS[1], KEYS[2], ARGV[1], ARGV[2]}"
|
||||||
|
|
||||||
|
|
||||||
class TestCmdScript(unittest.TestCase):
|
class TestCmdScript(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
pass
|
l.flushdb()
|
||||||
|
|
||||||
|
def test_eval(self):
|
||||||
|
assert l.eval(simple_script, ["key1", "key2"], "first", "second") == ["key1", "key2", "first", "second"]
|
||||||
|
|
||||||
|
def test_evalsha(self):
|
||||||
|
sha1 = l.scriptload(simple_script)
|
||||||
|
assert len(sha1) == 40
|
||||||
|
|
||||||
|
assert l.evalsha(sha1, ["key1", "key2"], "first", "second") == ["key1", "key2", "first", "second"]
|
||||||
|
|
||||||
|
def test_scriptload(self):
|
||||||
|
sha1 = l.scriptload(simple_script)
|
||||||
|
assert len(sha1) == 40
|
||||||
|
|
||||||
|
def test_scriptexists(self):
|
||||||
|
sha1 = l.scriptload(simple_script)
|
||||||
|
assert l.scriptexists(sha1) == [1L]
|
||||||
|
|
||||||
|
def test_scriptflush(self):
|
||||||
|
sha1 = l.scriptload(simple_script)
|
||||||
|
assert l.scriptexists(sha1) == [1L]
|
||||||
|
assert l.scriptflush() == 'OK'
|
||||||
|
|
||||||
|
assert l.scriptexists(sha1) == [0L]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def testEval(self):
|
|
||||||
assert l.eval("return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}", ["key1", "key2"], "first", "second") == ["key1", "key2", "first", "second"]
|
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ class TestCmdSet(unittest.TestCase):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
l.smclear('a', 'b', 'c')
|
l.flushdb()
|
||||||
|
|
||||||
def test_sadd(self):
|
def test_sadd(self):
|
||||||
members = set([b('1'), b('2'), b('3')])
|
members = set([b('1'), b('2'), b('3')])
|
||||||
|
|
|
@ -17,7 +17,7 @@ class TestCmdZset(unittest.TestCase):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
l.zclear('a')
|
l.flushdb()
|
||||||
|
|
||||||
def test_zadd(self):
|
def test_zadd(self):
|
||||||
l.zadd('a', a1=1, a2=2, a3=3)
|
l.zadd('a', a1=1, a2=2, a3=3)
|
||||||
|
|
|
@ -10,13 +10,14 @@ from ledis._compat import b
|
||||||
from ledis import ResponseError
|
from ledis import ResponseError
|
||||||
|
|
||||||
l = ledis.Ledis(port=6380)
|
l = ledis.Ledis(port=6380)
|
||||||
|
dbs = ["leveldb", "rocksdb", "goleveldb", "hyperleveldb", "lmdb", "boltdb"]
|
||||||
|
|
||||||
class TestOtherCommands(unittest.TestCase):
|
class TestOtherCommands(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
pass
|
l.flushdb()
|
||||||
|
|
||||||
# server information
|
# server information
|
||||||
def test_echo(self):
|
def test_echo(self):
|
||||||
|
@ -29,3 +30,92 @@ class TestOtherCommands(unittest.TestCase):
|
||||||
assert l.select('1')
|
assert l.select('1')
|
||||||
assert l.select('15')
|
assert l.select('15')
|
||||||
self.assertRaises(ResponseError, lambda: l.select('16'))
|
self.assertRaises(ResponseError, lambda: l.select('16'))
|
||||||
|
|
||||||
|
|
||||||
|
def test_info(self):
|
||||||
|
info1 = l.info()
|
||||||
|
assert info1.get("db_name") in dbs
|
||||||
|
info2 = l.info(section="server")
|
||||||
|
assert info2.get("os") in ["linux", "darwin"]
|
||||||
|
|
||||||
|
def test_flushdb(self):
|
||||||
|
l.set("a", 1)
|
||||||
|
assert l.flushdb() == "OK"
|
||||||
|
assert l.get("a") is None
|
||||||
|
|
||||||
|
def test_flushall(self):
|
||||||
|
l.select(1)
|
||||||
|
l.set("a", 1)
|
||||||
|
assert l.get("a") == b("1")
|
||||||
|
|
||||||
|
l.select(10)
|
||||||
|
l.set("a", 1)
|
||||||
|
assert l.get("a") == b("1")
|
||||||
|
|
||||||
|
assert l.flushall() == "OK"
|
||||||
|
|
||||||
|
assert l.get("a") is None
|
||||||
|
l.select(1)
|
||||||
|
assert l.get("a") is None
|
||||||
|
|
||||||
|
|
||||||
|
# test *scan commands
|
||||||
|
|
||||||
|
def check_keys(self, scan_type):
|
||||||
|
d = {
|
||||||
|
"scan": l.scan,
|
||||||
|
"sscan": l.sscan,
|
||||||
|
"lscan": l.lscan,
|
||||||
|
"hscan": l.hscan,
|
||||||
|
"zscan": l.zscan,
|
||||||
|
"bscan": l.bscan
|
||||||
|
}
|
||||||
|
|
||||||
|
key, keys = d[scan_type]()
|
||||||
|
assert key == ""
|
||||||
|
assert set(keys) == set([b("a"), b("b"), b("c")])
|
||||||
|
|
||||||
|
_, keys = d[scan_type](match="a")
|
||||||
|
assert set(keys) == set([b("a")])
|
||||||
|
|
||||||
|
_, keys = d[scan_type](key="a")
|
||||||
|
assert set(keys) == set([b("b"), b("c")])
|
||||||
|
|
||||||
|
|
||||||
|
def test_scan(self):
|
||||||
|
d = {"a":1, "b":2, "c": 3}
|
||||||
|
l.mset(d)
|
||||||
|
self.check_keys("scan")
|
||||||
|
|
||||||
|
|
||||||
|
def test_lscan(self):
|
||||||
|
l.rpush("a", 1)
|
||||||
|
l.rpush("b", 1)
|
||||||
|
l.rpush("c", 1)
|
||||||
|
self.check_keys("lscan")
|
||||||
|
|
||||||
|
|
||||||
|
def test_hscan(self):
|
||||||
|
l.hset("a", "hello", "world")
|
||||||
|
l.hset("b", "hello", "world")
|
||||||
|
l.hset("c", "hello", "world")
|
||||||
|
self.check_keys("hscan")
|
||||||
|
|
||||||
|
def test_sscan(self):
|
||||||
|
l.sadd("a", 1)
|
||||||
|
l.sadd("b", 2)
|
||||||
|
l.sadd("c", 3)
|
||||||
|
self.check_keys("sscan")
|
||||||
|
|
||||||
|
def test_zscan(self):
|
||||||
|
l.zadd("a", 1, "a")
|
||||||
|
l.zadd("b", 1, "a")
|
||||||
|
l.zadd("c", 1, "a")
|
||||||
|
self.check_keys("zscan")
|
||||||
|
|
||||||
|
def test_bscan(self):
|
||||||
|
l.bsetbit("a", 1, 1)
|
||||||
|
l.bsetbit("b", 1, 1)
|
||||||
|
l.bsetbit("c", 1, 1)
|
||||||
|
self.check_keys("bscan")
|
||||||
|
|
||||||
|
|
|
@ -4,14 +4,21 @@ sys.path.append("..")
|
||||||
|
|
||||||
import ledis
|
import ledis
|
||||||
|
|
||||||
|
global_l = ledis.Ledis()
|
||||||
|
|
||||||
|
#db that do not support transaction
|
||||||
|
dbs = ["leveldb", "rocksdb", "hyperleveldb", "goleveldb"]
|
||||||
|
check = global_l.info().get("db_name") in dbs
|
||||||
|
|
||||||
|
|
||||||
class TestTx(unittest.TestCase):
|
class TestTx(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.l = ledis.Ledis(port=6380)
|
self.l = ledis.Ledis(port=6380)
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
self.l.delete("a")
|
self.l.flushdb()
|
||||||
|
|
||||||
|
@unittest.skipIf(check, reason="db not support transaction")
|
||||||
def test_commit(self):
|
def test_commit(self):
|
||||||
tx = self.l.tx()
|
tx = self.l.tx()
|
||||||
self.l.set("a", "no-tx")
|
self.l.set("a", "no-tx")
|
||||||
|
@ -24,6 +31,7 @@ class TestTx(unittest.TestCase):
|
||||||
tx.commit()
|
tx.commit()
|
||||||
assert self.l.get("a") == "tx"
|
assert self.l.get("a") == "tx"
|
||||||
|
|
||||||
|
@unittest.skipIf(check, reason="db not support transaction")
|
||||||
def test_rollback(self):
|
def test_rollback(self):
|
||||||
tx = self.l.tx()
|
tx = self.l.tx()
|
||||||
self.l.set("a", "no-tx")
|
self.l.set("a", "no-tx")
|
||||||
|
|
Loading…
Reference in New Issue