Skip to content

Commit 960cac6

Browse files
committed
✨ feat: add set
1 parent 4f71473 commit 960cac6

File tree

4 files changed

+196
-4
lines changed

4 files changed

+196
-4
lines changed

.vscode/launch.json

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
// 使用 IntelliSense 了解相关属性。
3+
// 悬停以查看现有属性的描述。
4+
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
5+
"version": "0.2.0",
6+
"configurations": [
7+
{
8+
"name": "Python Debugger: Current File with Arguments",
9+
"type": "debugpy",
10+
"request": "launch",
11+
"program": "${file}",
12+
"console": "integratedTerminal",
13+
"args": "${command:pickArgs}"
14+
},
15+
{
16+
"name": "Python: pytest",
17+
"type": "debugpy",
18+
"request": "launch",
19+
"module": "pytest",
20+
"args": [
21+
"-v",
22+
"-s",
23+
"${workspaceFolder}/tests"
24+
],
25+
"console": "integratedTerminal",
26+
"cwd": "${workspaceFolder}",
27+
"env": {
28+
"PYTHONPATH": "${workspaceFolder}"
29+
}
30+
}
31+
]
32+
}

pyproject.toml

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@ version = "0.1.8"
44
description = ""
55
authors = ["miclon <jcnd@163.com>"]
66
readme = "README.md"
7-
packages = [
8-
{ include = 'use_redis', from = 'src' }
9-
]
7+
packages = [{ include = 'use_redis', from = 'src' }]
108

119
[tool.poetry.dependencies]
1210
python = "^3.8"
@@ -15,6 +13,7 @@ redis = ">=4.6.0,<6.0.0"
1513
[tool.poetry.group.test.dependencies]
1614
pylint = "*"
1715
pytest = "*"
16+
pytest-mock = "*"
1817
black = "*"
1918
flake8 = "*"
2019
isort = "*"
@@ -24,4 +23,3 @@ pre-commit-hooks = "*"
2423
[build-system]
2524
requires = ["poetry-core"]
2625
build-backend = "poetry.core.masonry.api"
27-

src/use_redis/set.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
from .store import RedisStore
2+
3+
4+
class RedisSetStore(RedisStore):
5+
def __init__(self, key, **kwargs):
6+
super().__init__(**kwargs)
7+
self._key = key
8+
9+
@property
10+
def key(self):
11+
return self._key
12+
13+
def add(self, *values):
14+
"""向集合中添加一个或多个成员"""
15+
return self.connection.sadd(self.key, *values)
16+
17+
def remove(self, *values):
18+
"""从集合中移除一个或多个成员"""
19+
return self.connection.srem(self.key, *values)
20+
21+
def members(self):
22+
"""返回集合中的所有成员"""
23+
return self.connection.smembers(self.key)
24+
25+
def is_member(self, value):
26+
"""判断value是否是集合的成员"""
27+
return self.connection.sismember(self.key, value)
28+
29+
def are_members(self, *values):
30+
"""判断多个value是否是集合的成员"""
31+
return self.connection.sismember(self.key, *values)
32+
33+
def size(self):
34+
"""返回集合的成员数"""
35+
return self.connection.scard(self.key)
36+
37+
def pop(self):
38+
"""随机移除并返回集合中的一个成员"""
39+
return self.connection.spop(self.key)
40+
41+
def move(self, dst_key, value):
42+
"""将成员从当前集合移动到另一个集合"""
43+
return self.connection.smove(self.key, dst_key, value)
44+
45+
def intersection(self, *other_keys):
46+
"""返回当前集合与其他集合的交集"""
47+
return self.connection.sinter(self.key, *other_keys)
48+
49+
def union(self, *other_keys):
50+
"""返回当前集合与其他集合的并集"""
51+
return self.connection.sunion(self.key, *other_keys)
52+
53+
def difference(self, *other_keys):
54+
"""返回当前集合与其他集合的差集"""
55+
return self.connection.sdiff(self.key, *other_keys)
56+
57+
def random_member(self):
58+
"""随机返回集合中的一个成员,但不删除"""
59+
return self.connection.srandmember(self.key)
60+
61+
def scan(self, cursor=0, match=None, count=None):
62+
"""迭代集合中的元素"""
63+
return self.connection.sscan(self.key, cursor, match, count)
64+
65+
def __getattr__(self, name):
66+
"""动态处理未实现的方法"""
67+
68+
def method(*args, **kwargs):
69+
redis_method = getattr(self.connection, name)
70+
return redis_method(self.key, *args, **kwargs)
71+
72+
return method

tests/test_set.py

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import pytest
2+
from use_redis.set import RedisSetStore
3+
4+
5+
@pytest.fixture
6+
def mock_redis(mocker):
7+
mock = mocker.patch("redis.Redis")
8+
return mock.return_value
9+
10+
11+
@pytest.fixture
12+
def set_store(mock_redis):
13+
return RedisSetStore("test_set")
14+
15+
16+
def test_add(set_store, mock_redis):
17+
set_store.add("value1", "value2")
18+
mock_redis.sadd.assert_called_once_with("test_set", "value1", "value2")
19+
20+
21+
def test_remove(set_store, mock_redis):
22+
set_store.remove("value1", "value2")
23+
mock_redis.srem.assert_called_once_with("test_set", "value1", "value2")
24+
25+
26+
def test_members(set_store, mock_redis):
27+
mock_redis.smembers.return_value = {"value1", "value2"}
28+
result = set_store.members()
29+
assert result == {"value1", "value2"}
30+
mock_redis.smembers.assert_called_once_with("test_set")
31+
32+
33+
def test_is_member(set_store, mock_redis):
34+
mock_redis.sismember.return_value = True
35+
result = set_store.is_member("value1")
36+
assert result is True
37+
mock_redis.sismember.assert_called_once_with("test_set", "value1")
38+
39+
40+
def test_size(set_store, mock_redis):
41+
mock_redis.scard.return_value = 2
42+
result = set_store.size()
43+
assert result == 2
44+
mock_redis.scard.assert_called_once_with("test_set")
45+
46+
47+
def test_pop(set_store, mock_redis):
48+
mock_redis.spop.return_value = "value1"
49+
result = set_store.pop()
50+
assert result == "value1"
51+
mock_redis.spop.assert_called_once_with("test_set")
52+
53+
54+
def test_move(set_store, mock_redis):
55+
set_store.move("dst_set", "value1")
56+
mock_redis.smove.assert_called_once_with("test_set", "dst_set", "value1")
57+
58+
59+
def test_intersection(set_store, mock_redis):
60+
set_store.intersection("other_set1", "other_set2")
61+
mock_redis.sinter.assert_called_once_with("test_set", "other_set1", "other_set2")
62+
63+
64+
def test_union(set_store, mock_redis):
65+
set_store.union("other_set1", "other_set2")
66+
mock_redis.sunion.assert_called_once_with("test_set", "other_set1", "other_set2")
67+
68+
69+
def test_difference(set_store, mock_redis):
70+
set_store.difference("other_set1", "other_set2")
71+
mock_redis.sdiff.assert_called_once_with("test_set", "other_set1", "other_set2")
72+
73+
74+
def test_random_member(set_store, mock_redis):
75+
mock_redis.srandmember.return_value = "value1"
76+
result = set_store.random_member()
77+
assert result == "value1"
78+
mock_redis.srandmember.assert_called_once_with("test_set")
79+
80+
81+
def test_scan(set_store, mock_redis):
82+
mock_redis.sscan.return_value = (0, ["value1", "value2"])
83+
result = set_store.scan(cursor=0, match="val*", count=2)
84+
assert result == (0, ["value1", "value2"])
85+
mock_redis.sscan.assert_called_once_with("test_set", 0, "val*", 2)
86+
87+
88+
def test_dynamic_method(set_store, mock_redis):
89+
set_store.srandmember(3)
90+
mock_redis.srandmember.assert_called_once_with("test_set", 3)

0 commit comments

Comments
 (0)