Skip to content

Commit 0f0d37c

Browse files
handle binary data accordingly
1 parent 0ebd42d commit 0f0d37c

3 files changed

Lines changed: 102 additions & 4 deletions

File tree

drift/instrumentation/redis/e2e-tests/src/app.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,79 @@ def test_pipeline_no_transaction():
134134
except Exception as e:
135135
return jsonify({"error": str(e)}), 500
136136

137+
@app.route("/test/async-pipeline", methods=["GET"])
138+
def test_async_pipeline():
139+
"""Test async pipeline operations using asyncio."""
140+
import asyncio
141+
import redis.asyncio as aioredis
142+
143+
async def run_async_pipeline():
144+
# Create async Redis client
145+
async_client = aioredis.Redis(
146+
host=os.getenv("REDIS_HOST", "redis"),
147+
port=int(os.getenv("REDIS_PORT", "6379")),
148+
db=0,
149+
decode_responses=True
150+
)
151+
152+
try:
153+
# Create async pipeline
154+
pipe = async_client.pipeline()
155+
pipe.set("test:async:pipe:key1", "async_value1")
156+
pipe.set("test:async:pipe:key2", "async_value2")
157+
pipe.get("test:async:pipe:key1")
158+
pipe.get("test:async:pipe:key2")
159+
results = await pipe.execute()
160+
161+
# Clean up
162+
await async_client.delete("test:async:pipe:key1", "test:async:pipe:key2")
163+
164+
return results
165+
finally:
166+
await async_client.aclose()
167+
168+
try:
169+
results = asyncio.run(run_async_pipeline())
170+
return jsonify({
171+
"success": True,
172+
"results": results
173+
})
174+
except Exception as e:
175+
return jsonify({"error": str(e)}), 500
176+
177+
@app.route("/test/binary-data", methods=["GET"])
178+
def test_binary_data():
179+
"""Test binary data that cannot be decoded as UTF-8."""
180+
try:
181+
# Create a Redis client without decode_responses for binary data
182+
binary_client = redis.Redis(
183+
host=os.getenv("REDIS_HOST", "redis"),
184+
port=int(os.getenv("REDIS_PORT", "6379")),
185+
db=0,
186+
decode_responses=False
187+
)
188+
189+
# Binary data that cannot be decoded as UTF-8
190+
binary_value = bytes([0x80, 0x81, 0x82, 0xFF, 0xFE, 0xFD])
191+
192+
# Set binary data
193+
binary_client.set("test:binary:key", binary_value)
194+
195+
# Get binary data back
196+
retrieved = binary_client.get("test:binary:key")
197+
198+
# Clean up
199+
binary_client.delete("test:binary:key")
200+
201+
return jsonify({
202+
"success": True,
203+
"original_hex": binary_value.hex(),
204+
"retrieved_hex": retrieved.hex() if retrieved else None,
205+
"match": binary_value == retrieved
206+
})
207+
except Exception as e:
208+
return jsonify({"error": str(e)}), 500
209+
137210

138211
if __name__ == "__main__":
139212
sdk.mark_app_as_ready()

drift/instrumentation/redis/e2e-tests/src/test_requests.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,10 @@ def make_request(method, endpoint, **kwargs):
5555
make_request("GET", "/test/pipeline-basic")
5656
make_request("GET", "/test/pipeline-no-transaction")
5757

58+
# Async Pipeline operations
59+
make_request("GET", "/test/async-pipeline")
60+
61+
# Binary data handling
62+
make_request("GET", "/test/binary-data")
63+
5864
print("\nAll requests completed successfully")

drift/instrumentation/redis/instrumentation.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -817,9 +817,10 @@ def _serialize_value(self, value: Any) -> Any:
817817
"""Serialize a single value for JSON."""
818818
if isinstance(value, bytes):
819819
try:
820-
return value.decode("utf-8")
820+
decoded = value.decode("utf-8")
821+
return {"__bytes__": True, "encoding": "utf8", "value": decoded}
821822
except UnicodeDecodeError:
822-
return value.hex()
823+
return {"__bytes__": True, "encoding": "hex", "value": value.hex()}
823824
elif isinstance(value, (str, int, float, bool, type(None))):
824825
return value
825826
elif isinstance(value, (list, tuple)):
@@ -835,6 +836,24 @@ def _serialize_response(self, response: Any) -> Any:
835836
"""Serialize Redis response for recording."""
836837
return self._serialize_value(response)
837838

839+
def _deserialize_value(self, value: Any) -> Any:
840+
"""Deserialize a value, converting typed wrappers back to original types."""
841+
if isinstance(value, dict):
842+
# Check for bytes wrapper
843+
if value.get("__bytes__") is True:
844+
encoding = value.get("encoding")
845+
data = value.get("value", "")
846+
if encoding == "utf8":
847+
return data.encode("utf-8")
848+
elif encoding == "hex":
849+
return bytes.fromhex(data)
850+
return data # fallback
851+
# Recursively deserialize dict values
852+
return {k: self._deserialize_value(v) for k, v in value.items()}
853+
elif isinstance(value, list):
854+
return [self._deserialize_value(v) for v in value]
855+
return value
856+
838857
def _deserialize_response(self, mock_data: dict[str, Any]) -> Any:
839858
"""Deserialize mocked response data from CLI.
840859
@@ -845,9 +864,9 @@ def _deserialize_response(self, mock_data: dict[str, Any]) -> Any:
845864

846865
if isinstance(mock_data, dict):
847866
if "result" in mock_data:
848-
return mock_data["result"]
867+
return self._deserialize_value(mock_data["result"])
849868
elif "results" in mock_data:
850-
return mock_data["results"]
869+
return [self._deserialize_value(r) for r in mock_data["results"]]
851870

852871
logger.warning(f"Could not deserialize mock_data structure: {mock_data}")
853872
return None

0 commit comments

Comments
 (0)