Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: Python package

on:
push:
branches: [ master, chore/updates ]
pull_request:
branches: [ master ]

permissions:
contents: read

jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13"]

steps:
- uses: actions/checkout@v6.0.3

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v6.2.0
with:
python-version: ${{ matrix.python-version }}

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install tox

- name: Run tests with tox
run: |
tox -e py

- name: Run style checks with tox (Python 3.12 only)
if: ${{ matrix.python-version == '3.12' }}
run: |
tox -e codestyle
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,25 @@
`STORAGE_CONFIG` will send any option specified as an argument to `redis.StrictRedis`.

3. Start your bot in text mode: `errbot -T` to give it a shot.

### Running Tests

To run the automated test suite:

1. Create and activate a Python virtual environment:
```bash
python3 -m venv .venv
source .venv/bin/activate
```
2. Install the dependencies and testing requirements:
```bash
pip install -r requirements.txt errbot pytest tox fakeredis
```
3. Run the tests using `pytest` directly:
```bash
pytest
```
Or run both tests and style checks using `tox`:
```bash
tox
```
6 changes: 3 additions & 3 deletions redisstorage.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def get(self, key: str) -> Any:
result = self.redis.get(unique_key)
if result is None:
raise KeyError("%s doesn't exists." % (unique_key))
return decode(result.decode())
return decode(result.decode(), keys=True)

def remove(self, key: str):
unique_key = self._make_nskey(key)
Expand All @@ -52,8 +52,8 @@ def remove(self, key: str):

def set(self, key: str, value: Any) -> None:
unique_key = self._make_nskey(key)
log.debug("Setting value '%s' at '%s'", encode(value), unique_key)
self.redis.set(unique_key, encode(value))
log.debug("Setting value '%s' at '%s'", encode(value, keys=True), unique_key)
self.redis.set(unique_key, encode(value, keys=True))

def len(self):
return len(self.keys())
Expand Down
4 changes: 2 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
jsonpickle
redis
jsonpickle>=3.0.0
redis>=5.0.0
117 changes: 117 additions & 0 deletions test_redis.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import logging

import fakeredis
import pytest

import redisstorage


class MockBotConfig:
def __init__(self, storage_config=None):
self.STORAGE_CONFIG = storage_config if storage_config is not None else {}
self.BOT_LOG_LEVEL = logging.WARNING


def test_storage_operations(monkeypatch):
fake_redis = fakeredis.FakeStrictRedis()
monkeypatch.setattr(redisstorage.redis, "StrictRedis", lambda **kwargs: fake_redis)

config = MockBotConfig()
plugin = redisstorage.RedisPlugin(config)
storage = plugin.open("test_namespace")

# Initial state
assert storage.len() == 0
assert list(storage.keys()) == []

# Get non-existent
with pytest.raises(KeyError):
storage.get("key1")

# Set and get basic values
storage.set("key1", "value1")
assert storage.len() == 1
assert storage.get("key1") == "value1"
assert list(storage.keys()) == ["key1"]

# Set and get complex objects (list, dict, int)
storage.set("key2", {"a": 1, "b": [2, 3]})
assert storage.len() == 2
assert storage.get("key2") == {"a": 1, "b": [2, 3]}
assert set(storage.keys()) == {"key1", "key2"}

# Overwrite value
storage.set("key1", "new_value1")
assert storage.get("key1") == "new_value1"

# Remove non-existent key
with pytest.raises(KeyError):
storage.remove("nonexistent")

# Remove existing key
storage.remove("key1")
assert storage.len() == 1
with pytest.raises(KeyError):
storage.get("key1")

assert list(storage.keys()) == ["key2"]

# Close storage
storage.close()


def test_multiple_namespaces(monkeypatch):
fake_redis = fakeredis.FakeStrictRedis()
monkeypatch.setattr(redisstorage.redis, "StrictRedis", lambda **kwargs: fake_redis)

config = MockBotConfig()
plugin = redisstorage.RedisPlugin(config)

storage_a = plugin.open("namespace_a")
storage_b = plugin.open("namespace_b")

storage_a.set("shared_key", "value_a")
storage_b.set("shared_key", "value_b")

assert storage_a.get("shared_key") == "value_a"
assert storage_b.get("shared_key") == "value_b"

assert storage_a.len() == 1
assert storage_b.len() == 1

storage_a.close()
storage_b.close()


def test_simple_store_retrieve(monkeypatch):
from errbot.storage import StoreMixin

fake_redis = fakeredis.FakeStrictRedis()
monkeypatch.setattr(redisstorage.redis, "StrictRedis", lambda **kwargs: fake_redis)

config = MockBotConfig()
plugin = redisstorage.RedisPlugin(config)
sm = StoreMixin()
sm.open_storage(plugin, "ns")
sm["toto"] = "titui"
assert sm["toto"] == "titui"
sm.close_storage()


def test_mutable(monkeypatch):
from errbot.storage import StoreMixin

fake_redis = fakeredis.FakeStrictRedis()
monkeypatch.setattr(redisstorage.redis, "StrictRedis", lambda **kwargs: fake_redis)

config = MockBotConfig()
plugin = redisstorage.RedisPlugin(config)
sm = StoreMixin()
sm.open_storage(plugin, "ns")
sm["toto"] = [1, 3]

with sm.mutable("toto") as titi:
titi[1] = 5

assert sm["toto"] == [1, 5]
sm.close_storage()
21 changes: 21 additions & 0 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[tox]
envlist = py, codestyle
skipsdist = True

[testenv]
deps =
pytest
jsonpickle>=3.0.0
redis
fakeredis
errbot
commands = pytest -v

[testenv:codestyle]
deps =
ruff
isort
commands =
ruff check redisstorage.py test_redis.py
ruff format --check redisstorage.py test_redis.py
isort --check-only redisstorage.py test_redis.py