Skip to content

Commit

Permalink
Start skeleton for streams commands
Browse files Browse the repository at this point in the history
  • Loading branch information
cunla committed Jan 26, 2023
1 parent d67692f commit fca6870
Show file tree
Hide file tree
Showing 5 changed files with 170 additions and 105 deletions.
2 changes: 2 additions & 0 deletions fakeredis/_fakesocket.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from .commands_mixins.server_mixin import ServerCommandsMixin
from .commands_mixins.set_mixin import SetCommandsMixin
from .commands_mixins.sortedset_mixin import SortedSetCommandsMixin
from .commands_mixins.streams_mixin import StreamsCommandsMixin
from .commands_mixins.string_mixin import StringCommandsMixin
from .commands_mixins.transactions_mixin import TransactionsCommandsMixin

Expand All @@ -28,6 +29,7 @@ class FakeSocket(
SetCommandsMixin,
BitmapCommandsMixin,
SortedSetCommandsMixin,
StreamsCommandsMixin,
JSONCommandsMixin,
):

Expand Down
3 changes: 3 additions & 0 deletions fakeredis/commands_mixins/streams_mixin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class StreamsCommandsMixin:
# Streams commands
pass
63 changes: 63 additions & 0 deletions test/test_json/test_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -322,3 +322,66 @@ def test_decode_null(r: redis.Redis):

def test_decode_response_disabaled_null(r: redis.Redis):
assert r.json().get("abc") is None


def test_json_get_jset(r: redis.Redis) -> None:
assert r.json().set("foo", Path.root_path(), "bar", ) == 1
assert "bar" == r.json().get("foo")
assert r.json().get("baz") is None
assert 1 == r.json().delete("foo")
assert r.exists("foo") == 0


def test_nonascii_setgetdelete(r: redis.Redis) -> None:
assert r.json().set("not-ascii", Path.root_path(), "hyvää-élève", )
assert "hyvää-élève" == r.json().get("not-ascii", no_escape=True, )
assert 1 == r.json().delete("not-ascii")
assert r.exists("not-ascii") == 0


def test_json_setbinarykey(r: redis.Redis) -> None:
data = {"hello": "world", b"some": "value"}

with pytest.raises(TypeError):
r.json().set("some-key", Path.root_path(), data)

assert r.json().set("some-key", Path.root_path(), data, decode_keys=True)


def test_set_file(r: redis.Redis) -> None:
# Standard Library Imports
import json
import tempfile

obj = {"hello": "world"}
jsonfile = tempfile.NamedTemporaryFile(suffix=".json")
with open(jsonfile.name, "w+") as fp:
fp.write(json.dumps(obj))

no_json_file = tempfile.NamedTemporaryFile()
no_json_file.write(b"Hello World")

assert r.json().set_file("test", Path.root_path(), jsonfile.name)
assert r.json().get("test") == obj
with pytest.raises(json.JSONDecodeError):
r.json().set_file("test2", Path.root_path(), no_json_file.name)


def test_set_path(r: redis.Redis) -> None:
# Standard Library Imports
import json
import tempfile

root = tempfile.mkdtemp()
sub = tempfile.mkdtemp(dir=root)
jsonfile = tempfile.mktemp(suffix=".json", dir=sub)
no_json_file = tempfile.mktemp(dir=root)

with open(jsonfile, "w+") as fp:
fp.write(json.dumps({"hello": "world"}))
with open(no_json_file, "a+") as fp:
fp.write("hello")

result = {jsonfile: True, no_json_file: False}
assert r.json().set_path(Path.root_path(), root) == result
assert r.json().get(jsonfile.rsplit(".")[0]) == {"hello": "world"}
106 changes: 1 addition & 105 deletions test/test_json/test_json_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@

from __future__ import annotations

from typing import (Any, Dict, List, Tuple, )

import pytest
import redis
from redis import exceptions
from redis.commands.json.decoders import decode_list, unstring
from redis.commands.json.path import Path
from typing import (Any, Dict, List, Tuple, )

json_tests = pytest.importorskip("jsonpath_ng")

Expand Down Expand Up @@ -64,37 +62,6 @@ def json_data() -> Dict[str, Any]:
}


def test_json_setbinarykey(r: redis.Redis) -> None:
data = {"hello": "world", b"some": "value"}

with pytest.raises(TypeError):
r.json().set("some-key", Path.root_path(), data)

result = r.json().set(
"some-key",
Path.root_path(),
data,
decode_keys=True,
)

assert result


def test_json_get_jset(r: redis.Redis) -> None:
assert r.json().set("foo", Path.root_path(), "bar", ) == 1
assert "bar" == r.json().get("foo")
assert r.json().get("baz") is None
assert 1 == r.json().delete("foo")
assert r.exists("foo") == 0


def test_nonascii_setgetdelete(r: redis.Redis) -> None:
assert r.json().set("not-ascii", Path.root_path(), "hyvää-élève", )
assert "hyvää-élève" == r.json().get("not-ascii", no_escape=True, )
assert 1 == r.json().delete("not-ascii")
assert r.exists("not-ascii") == 0


@pytest.mark.xfail
def test_type(r: redis.Redis) -> None:
r.json().set("1", Path.root_path(), 1, )
Expand Down Expand Up @@ -1077,74 +1044,3 @@ def test_arrindex_dollar(r: redis.Redis) -> None:
# Test index of None scalar in single value
assert r.json().arrindex("test_None", ".[0].arr", "None") == 1
assert r.json().arrindex("test_None", "..nested2_not_found.arr", "None") == 0


# @pytest.mark.xfail
def test_decoders_and_unstring():
assert unstring("4") == 4
assert unstring("45.55") == 45.55
assert unstring("hello world") == "hello world"

assert decode_list(b"45.55") == 45.55
assert decode_list("45.55") == 45.55
assert decode_list(["hello", b"world"]) == ["hello", "world"]


# noinspection PyUnresolvedReferences
@pytest.mark.xfail
def test_custom_decoder(r: redis.Redis) -> None:
# Standard Library Imports
import json

# Third-Party Imports
import orjson

cj = r.json(encoder=orjson, decoder=orjson)
assert cj.set("foo", Path.root_path(), "bar")
assert "bar" == cj.get("foo")
assert cj.get("baz") is None
assert 1 == cj.delete("foo")
assert r.exists("foo") == 0
assert not isinstance(cj.__encoder__, json.JSONEncoder)
assert not isinstance(cj.__decoder__, json.JSONDecoder)


# @pytest.mark.xfail
def test_set_file(r: redis.Redis) -> None:
# Standard Library Imports
import json
import tempfile

obj = {"hello": "world"}
jsonfile = tempfile.NamedTemporaryFile(suffix=".json")
with open(jsonfile.name, "w+") as fp:
fp.write(json.dumps(obj))

no_json_file = tempfile.NamedTemporaryFile()
no_json_file.write(b"Hello World")

assert r.json().set_file("test", Path.root_path(), jsonfile.name)
assert r.json().get("test") == obj
with pytest.raises(json.JSONDecodeError):
r.json().set_file("test2", Path.root_path(), no_json_file.name)


# @pytest.mark.xfail
def test_set_path(r: redis.Redis) -> None:
# Standard Library Imports
import json
import tempfile

root = tempfile.mkdtemp()
sub = tempfile.mkdtemp(dir=root)
jsonfile = tempfile.mktemp(suffix=".json", dir=sub)
no_json_file = tempfile.mktemp(dir=root)

with open(jsonfile, "w+") as fp:
fp.write(json.dumps({"hello": "world"}))
with open(no_json_file, "a+") as fp:
fp.write("hello")

result = {jsonfile: True, no_json_file: False}
assert r.json().set_path(Path.root_path(), root) == result
assert r.json().get(jsonfile.rsplit(".")[0]) == {"hello": "world"}
101 changes: 101 additions & 0 deletions test/test_mixins/test_streams_commands.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import pytest
import re

import redis


@pytest.mark.xfail
def test_xrevrange(r: redis.Redis):
stream = "stream"
message_id = r.xadd(stream, {"foo": "bar"})
assert re.match(rb"[0-9]+\-[0-9]+", message_id)

# explicit message id
message_id = b"9999999999999999999-0"
assert message_id == r.xadd(stream, {"foo": "bar"}, id=message_id)

# with maxlen, the list evicts the first message
r.xadd(stream, {"foo": "bar"}, maxlen=2, approximate=False)
assert r.xlen(stream) == 2


@pytest.mark.xfail
def test_xadd_nomkstream(r: redis.Redis):
# nomkstream option
stream = "stream"
r.xadd(stream, {"foo": "bar"})
r.xadd(stream, {"some": "other"}, nomkstream=False)
assert r.xlen(stream) == 2
r.xadd(stream, {"some": "other"}, nomkstream=True)
assert r.xlen(stream) == 3


@pytest.mark.xfail
def test_xadd_minlen_and_limit(r: redis.Redis):
stream = "stream"

r.xadd(stream, {"foo": "bar"})
r.xadd(stream, {"foo": "bar"})
r.xadd(stream, {"foo": "bar"})
r.xadd(stream, {"foo": "bar"})

# Future self: No limits without approximate, according to the api
with pytest.raises(redis.ResponseError):
assert r.xadd(stream, {"foo": "bar"}, maxlen=3, approximate=False, limit=2)

# limit can not be provided without maxlen or minid
with pytest.raises(redis.ResponseError):
assert r.xadd(stream, {"foo": "bar"}, limit=2)

# maxlen with a limit
assert r.xadd(stream, {"foo": "bar"}, maxlen=3, approximate=True, limit=2)
r.delete(stream)

# maxlen and minid can not be provided together
with pytest.raises(redis.DataError):
assert r.xadd(stream, {"foo": "bar"}, maxlen=3, minid="sometestvalue")

# minid with a limit
m1 = r.xadd(stream, {"foo": "bar"})
r.xadd(stream, {"foo": "bar"})
r.xadd(stream, {"foo": "bar"})
r.xadd(stream, {"foo": "bar"})
assert r.xadd(stream, {"foo": "bar"}, approximate=True, minid=m1, limit=3)

# pure minid
r.xadd(stream, {"foo": "bar"})
r.xadd(stream, {"foo": "bar"})
r.xadd(stream, {"foo": "bar"})
m4 = r.xadd(stream, {"foo": "bar"})
assert r.xadd(stream, {"foo": "bar"}, approximate=False, minid=m4)

# minid approximate
r.xadd(stream, {"foo": "bar"})
r.xadd(stream, {"foo": "bar"})
m3 = r.xadd(stream, {"foo": "bar"})
r.xadd(stream, {"foo": "bar"})
assert r.xadd(stream, {"foo": "bar"}, approximate=True, minid=m3)


@pytest.mark.xfail
def test_xrevrange(r: redis.Redis):
stream = "stream"
m1 = r.xadd(stream, {"foo": "bar"})
m2 = r.xadd(stream, {"foo": "bar"})
m3 = r.xadd(stream, {"foo": "bar"})
m4 = r.xadd(stream, {"foo": "bar"})

def get_ids(results):
return [result[0] for result in results]

results = r.xrevrange(stream, max=m4)
assert get_ids(results) == [m4, m3, m2, m1]

results = r.xrevrange(stream, max=m3, min=m2)
assert get_ids(results) == [m3, m2]

results = r.xrevrange(stream, min=m3)
assert get_ids(results) == [m4, m3]

results = r.xrevrange(stream, min=m2, count=1)
assert get_ids(results) == [m4]

0 comments on commit fca6870

Please sign in to comment.