From 9e6b63c3b440e74fec005540fbbbd5643c82b979 Mon Sep 17 00:00:00 2001 From: Eugene Molotov Date: Sat, 10 Aug 2024 17:52:11 +0500 Subject: [PATCH] Adapt to redis 3.* MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-By: Markus Gärtner --- .github/workflows/test.yml | 23 ++++++ python/minqlx/database.py | 54 +++++++++++++- python/tests/database.py | 1 + python/tests/test_database.py | 137 ++++++++++++++++++++++++++++++++++ 4 files changed, 214 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/test.yml create mode 120000 python/tests/database.py create mode 100644 python/tests/test_database.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..3e9b821 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,23 @@ +name: Test + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + + strategy: + matrix: + redis-version: ["2.*", "3.*", "4.*", "5.*"] + os: ["ubuntu-24.04", "ubuntu-22.04", "ubuntu-20.04"] + + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v3 + - name: Install redis package + run: sudo python3 -m pip install redis==${{ matrix.redis-version }} + - name: Run test + run: python3 -m unittest discover python/tests/ diff --git a/python/minqlx/database.py b/python/minqlx/database.py index 9f42c00..242a12a 100644 --- a/python/minqlx/database.py +++ b/python/minqlx/database.py @@ -16,7 +16,11 @@ # You should have received a copy of the GNU General Public License # along with minqlx. If not, see . -import minqlx +try: + import minqlx +except ImportError: + pass + import redis # ==================================================================== @@ -313,3 +317,51 @@ def close(self): if Redis._pool: Redis._pool.disconnect() Redis._pool = None + + if redis.VERSION > (3,): + def lrem(self, name, value, count): + return self.r.lrem(name, count, value) + + def setex(self, name, value, time): + return self.r.setex(name, time, value) + + def zincrby(self, name, value, amount=1): + return self.r.zincrby(name, amount, value) + + def zadd(self, name, *args, **kwargs): + if len(args) == 1 and isinstance(args[0], dict): + # redis >= 3.* arguments given + return self.r.zadd(name, *args, **kwargs) + + if len(args) > 0 and len(args) % 2 != 0: + raise redis.RedisError("ZADD requires an equal number of values and scores") + + mapping = {} + for i in range(0, len(args), 2): + mapping[args[i + 1]] = args[i] + + return self.r.zadd(name, mapping, **kwargs) + + def msetnx(self, *args, **kwargs): + mapping = {} + if args: + if len(args) != 1 or not isinstance(args[0], dict): + raise redis.RedisError("MSETNX requires **kwargs or a single dict arg") + mapping.update(args[0]) + + if kwargs: + mapping.update(kwargs) + + return self.r.msetnx(mapping) + + def mset(self, *args, **kwargs): + mapping = {} + if args: + if len(args) != 1 or not isinstance(args[0], dict): + raise redis.RedisError("MSET requires **kwargs or a single dict arg") + mapping.update(args[0]) + + if kwargs: + mapping.update(kwargs) + + return self.r.mset(mapping) diff --git a/python/tests/database.py b/python/tests/database.py new file mode 120000 index 0000000..69549ee --- /dev/null +++ b/python/tests/database.py @@ -0,0 +1 @@ +../minqlx/database.py \ No newline at end of file diff --git a/python/tests/test_database.py b/python/tests/test_database.py new file mode 100644 index 0000000..94229bd --- /dev/null +++ b/python/tests/test_database.py @@ -0,0 +1,137 @@ +from unittest import TestCase +import database +import random +import string +from time import sleep + + +def random_string(): + return "".join([random.choice(string.ascii_letters) for x in range(10)]) + + +class RedisRegressionTestCase(TestCase): + def setUp(self): + self.c = database.Redis(None) + self.c.connect("127.0.0.1:6379") + + def test_zadd_01(self): + c = self.c + key = "zadd_01_key_" + random_string() + + c.zadd(key, 5, "player1", 6, "player2") + res = dict(c.zrange(key, 0, -1, withscores=True)) + self.assertEqual(res["player1"], 5) + self.assertEqual(res["player2"], 6) + + c.zadd(key, 2, "player1") + res = dict(c.zrange(key, 0, -1, withscores=True)) + self.assertEqual(res["player1"], 2) + self.assertEqual(res["player2"], 6) + + c.zadd(key, 3, "player1", xx=True) + res = dict(c.zrange(key, 0, -1, withscores=True)) + self.assertEqual(res["player1"], 3) + + c.zadd(key, 3, "player_does_not_exist", xx=True) + res = dict(c.zrange(key, 0, -1, withscores=True)) + self.assertNotIn("player_does_not_exist", res) + + def test_zincrby_01(self): + key = "zincrby_01_key_" + random_string() + c = self.c + + c.zincrby(key, "FIELD1") + c.zincrby(key, "FIELD2", 2) + res = dict(c.zrange(key, 0, -1, withscores=True)) + self.assertEqual(res["FIELD1"], 1) + self.assertEqual(res["FIELD2"], 2) + + c.zincrby(key, "FIELD1") + c.zincrby(key, "FIELD2", 3) + res = dict(c.zrange(key, 0, -1, withscores=True)) + self.assertEqual(res["FIELD1"], 2) + self.assertEqual(res["FIELD2"], 5) + + def test_lrem_01(self): + key = "lrem_01_key_" + random_string() + c = self.c + + c.rpush( + key, + "Cat", + "Dog", + "Horse", + "Cat", + "Dog", + "Cat", + "Monkey", + "Dog", + "Cat", + "Dog", + "Buffalo", + ) + c.lrem(key, "Cat", 2) + result = c.lrange(key, 0, -1) + self.assertEqual( + result, + ["Dog", "Horse", "Dog", "Cat", "Monkey", "Dog", "Cat", "Dog", "Buffalo"], + ) + + def test_setex_01(self): + key = "setex_01_key_" + random_string() + c = self.c + test_value = "test value" + timeout = 1 + + c.setex(key, test_value, timeout) + self.assertEqual(c.get(key), test_value, "Too small timeout value?") + sleep(timeout + 0.5) + self.assertEqual(c.get(key), None) + + def test_mset_01(self): + c = self.c + c.mset(field1="example1", field2="example2") + self.assertEqual(["example1", "example2"], c.mget("field1", "field2")) + + def test_mset_02(self): + c = self.c + c.mset({"field3": "example1", "field4": "example2"}) + self.assertEqual(["example1", "example2"], c.mget("field3", "field4")) + + def test_msetnx_01_kwargs(self): + c = self.c + prefix = "msetnx_01_key_" + random_string() + key1 = prefix + "_field1" + key2 = prefix + "_field2" + d = { + key1: "example1", + key2: "example2", + } + c.msetnx(**d) + self.assertEqual(["example1", "example2"], c.mget(key1, key2)) + + d = { + key1: "value should not be used", + key2: "same here", + } + c.msetnx(**d) + self.assertEqual(["example1", "example2"], c.mget(key1, key2)) + + def test_msetnx_02_dict(self): + c = self.c + prefix = "msetnx_02_key_" + random_string() + key1 = prefix + "_field1" + key2 = prefix + "_field2" + d = { + key1: "example1", + key2: "example2", + } + c.msetnx(d) + self.assertEqual(["example1", "example2"], c.mget(key1, key2)) + + d = { + key1: "value should not be used", + key2: "same here", + } + c.msetnx(d) + self.assertEqual(["example1", "example2"], c.mget(key1, key2))