Skip to content
Open
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
9 changes: 5 additions & 4 deletions redis/commands/json/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -346,10 +346,11 @@ def _walk_directory(folder: str) -> list[str]:

for file_path in file_paths:
try:
# TODO: rsplit(".") splits on all dots, mishandling paths
# with dots in directories (e.g. /data/v1.2/file.json).
# Should be rsplit(".", 1) — fix in a separate PR.
file_name = file_path.rsplit(".")[0]
# Strip only the file extension (the final dot-separated
# component). Using rsplit(".", 1) avoids truncating paths
# that contain dots in a directory or file name, e.g.
# /data/v1.2/file.json -> /data/v1.2/file.
file_name = file_path.rsplit(".", 1)[0]
await self.set_file(
file_name,
json_path,
Expand Down
9 changes: 5 additions & 4 deletions redis/commands/json/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -704,10 +704,11 @@ def set_path(
for file in files:
file_path = os.path.join(root, file)
try:
# TODO: rsplit(".") splits on all dots, mishandling paths
# with dots in directories (e.g. /data/v1.2/file.json).
# Should be rsplit(".", 1) — fix in a separate PR.
file_name = file_path.rsplit(".")[0]
# Strip only the file extension (the final dot-separated
# component). Using rsplit(".", 1) avoids truncating paths
# that contain dots in a directory or file name, e.g.
# /data/v1.2/file.json -> /data/v1.2/file.
file_name = file_path.rsplit(".", 1)[0]
self.set_file(
file_name,
json_path,
Expand Down
32 changes: 32 additions & 0 deletions tests/test_asyncio/test_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -1142,3 +1142,35 @@ async def test_toggle_dollar(decoded_r: redis.Redis):
# Test missing key
with pytest.raises(exceptions.ResponseError):
await decoded_r.json().toggle("non_existing_doc", "$..a")


async def test_set_path_key_strips_only_extension(monkeypatch):
# Regression test: JSON.set_path must strip only the file extension when
# deriving the key, so a path containing dots in a directory (e.g. a
# versioned "v1.2" folder) is not truncated at the first dot. set_file is
# stubbed so this runs without a server.
import json

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please move those imports to the top level in the import section.

import os
import tempfile

root = tempfile.mkdtemp()
dotted_dir = os.path.join(root, "v1.2")
os.makedirs(dotted_dir)
json_file = os.path.join(dotted_dir, "data.json")
with open(json_file, "w") as fp:
fp.write(json.dumps({"hello": "world"}))

json_client = redis.Redis().json()
captured = []

async def fake_set_file(name, *args, **kwargs):
captured.append(name)
return True

monkeypatch.setattr(json_client, "set_file", fake_set_file)

result = await json_client.set_path(Path.root_path(), root)

expected_key = json_file[: -len(".json")]
assert captured == [expected_key]
assert result == {json_file: True}
41 changes: 39 additions & 2 deletions tests/test_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -1620,10 +1620,15 @@ def test_set_file(client):
@pytest.mark.redismod
def test_set_path(client):
import json

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same note for the imports.

import os
import tempfile

root = tempfile.mkdtemp()
sub = tempfile.mkdtemp(dir=root)
# Place the file under a directory whose name contains a dot, to guard
# against the derived key being truncated at the first dot rather than at
# the file extension.
sub = os.path.join(root, "v1.2")
os.makedirs(sub)
jsonfile = tempfile.mkstemp(suffix=".json", dir=sub)[1]
nojsonfile = tempfile.mkstemp(dir=root)[1]

Expand All @@ -1635,4 +1640,36 @@ def test_set_path(client):
result = {jsonfile: True, nojsonfile: False}
assert client.json().set_path(Path.root_path(), root) == result
res = {"hello": "world"}
assert client.json().get(jsonfile.rsplit(".")[0]) == res
assert client.json().get(jsonfile.rsplit(".", 1)[0]) == res


def test_set_path_key_strips_only_extension(monkeypatch):
# Regression test: JSON.set_path must strip only the file extension when
# deriving the key, so a path containing dots in a directory (e.g. a
# versioned "v1.2" folder) is not truncated at the first dot. set_file is
# stubbed so this runs without a server.
import json
import os
import tempfile

root = tempfile.mkdtemp()
dotted_dir = os.path.join(root, "v1.2")
os.makedirs(dotted_dir)
json_file = os.path.join(dotted_dir, "data.json")
with open(json_file, "w") as fp:
fp.write(json.dumps({"hello": "world"}))

json_client = redis.Redis().json()
captured = []

def fake_set_file(name, *args, **kwargs):
captured.append(name)
return True

monkeypatch.setattr(json_client, "set_file", fake_set_file)

result = json_client.set_path(Path.root_path(), root)

expected_key = json_file[: -len(".json")]
assert captured == [expected_key]
assert result == {json_file: True}