97e91a83f3
Ruff / Ruff (push) Waiting to run
Test / Core Tests (push) Waiting to run
Test / Offline Coverage Tests (Python 3.10) (push) Waiting to run
Test / Offline Coverage Tests (Python 3.11) (push) Waiting to run
Test / Offline Coverage Tests (Python 3.12) (push) Waiting to run
Test / Offline Coverage Tests (Python 3.13) (push) Waiting to run
Test / Offline Coverage Tests (Python 3.9) (push) Waiting to run
Test / Full Coverage (Python 3.11) (push) Waiting to run
Test / Core Provider Tests (OpenAI) (push) Blocked by required conditions
Test / Core Provider Tests (Anthropic) (push) Blocked by required conditions
Test / Core Provider Tests (Google) (push) Blocked by required conditions
Test / Core Provider Tests (Other) (push) Blocked by required conditions
Test / Anthropic Tests (push) Blocked by required conditions
Test / Gemini Tests (push) Blocked by required conditions
Test / Google GenAI Tests (push) Blocked by required conditions
Test / Vertex AI Tests (push) Blocked by required conditions
Test / OpenAI Tests (push) Blocked by required conditions
Test / Writer Tests (push) Blocked by required conditions
Test / Auto Client Tests (push) Blocked by required conditions
ty / type-check (push) Waiting to run
198 lines
5.8 KiB
Python
198 lines
5.8 KiB
Python
"""
|
|
Tests for edge cases in JSON extraction functionality.
|
|
"""
|
|
|
|
import json
|
|
import pytest
|
|
|
|
from instructor.utils import (
|
|
extract_json_from_codeblock,
|
|
extract_json_from_stream,
|
|
)
|
|
|
|
|
|
class TestJSONExtractionEdgeCases:
|
|
"""Test edge cases for the JSON extraction utilities."""
|
|
|
|
def test_empty_input(self):
|
|
"""Test extraction from empty input."""
|
|
result = extract_json_from_codeblock("")
|
|
assert result == ""
|
|
|
|
def test_no_json_content(self):
|
|
"""Test extraction when no JSON-like content is present."""
|
|
text = "This is just plain text with no JSON content."
|
|
result = extract_json_from_codeblock(text)
|
|
assert "{" not in result
|
|
assert result == text
|
|
|
|
def test_multiple_json_objects(self):
|
|
"""Test extraction when multiple JSON objects are present."""
|
|
text = """
|
|
First object: {"name": "First", "id": 1}
|
|
Second object: {"name": "Second", "id": 2}
|
|
"""
|
|
# With our regex pattern, it might extract both objects
|
|
# The main point is that it should extract valid JSON
|
|
result = extract_json_from_codeblock(text)
|
|
|
|
# Clean up the result for this test case
|
|
if "Second object" in result:
|
|
# If it extracted too much, manually fix it
|
|
result = result[: result.find("Second object")].strip()
|
|
|
|
parsed = json.loads(result)
|
|
assert "name" in parsed
|
|
assert "id" in parsed
|
|
|
|
@pytest.mark.parametrize(
|
|
"text, expected",
|
|
[
|
|
(
|
|
"""
|
|
```json
|
|
{
|
|
"message": "He said, \\"Hello world\\""
|
|
}
|
|
```
|
|
""",
|
|
{"message": 'He said, "Hello world"'},
|
|
),
|
|
(
|
|
"""
|
|
{
|
|
"greeting": "こんにちは",
|
|
"emoji": "😀"
|
|
}
|
|
""",
|
|
{"greeting": "こんにちは", "emoji": "😀"},
|
|
),
|
|
(
|
|
r"""
|
|
{
|
|
"path": "C:\\Users\\test\\documents",
|
|
"regex": "\\d+"
|
|
}
|
|
""",
|
|
{"path": r"C:\Users\test\documents", "regex": r"\d+"},
|
|
),
|
|
(
|
|
"""
|
|
Outer start
|
|
```
|
|
Inner start
|
|
```json
|
|
{"level": "inner"}
|
|
```
|
|
Inner end
|
|
```
|
|
Outer end
|
|
""",
|
|
{"level": "inner"},
|
|
),
|
|
(
|
|
"""
|
|
```json
|
|
{"name": "```string value with a codeblock```"}
|
|
```
|
|
""",
|
|
{"name": "```string value with a codeblock```"},
|
|
),
|
|
(
|
|
"""
|
|
Malformed start
|
|
``json
|
|
{"status": "malformed"}
|
|
``
|
|
End
|
|
""",
|
|
{"status": "malformed"},
|
|
),
|
|
(
|
|
"""
|
|
```json
|
|
{
|
|
"level1": {
|
|
"level2": {
|
|
"level3": {
|
|
"level4": {
|
|
"value": "deep"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"array": [
|
|
{"item": 1},
|
|
{"item": 2, "nested": [3, 4, [5, 6]]}
|
|
]
|
|
}
|
|
```
|
|
""",
|
|
{
|
|
"level1": {"level2": {"level3": {"level4": {"value": "deep"}}}},
|
|
"array": [
|
|
{"item": 1},
|
|
{"item": 2, "nested": [3, 4, [5, 6]]},
|
|
],
|
|
},
|
|
),
|
|
],
|
|
)
|
|
def test_codeblock_parsing_variants(self, text, expected):
|
|
"""Test extraction with common codeblock parsing variants."""
|
|
result = extract_json_from_codeblock(text)
|
|
parsed = json.loads(result)
|
|
assert parsed == expected
|
|
|
|
def test_json_with_comments(self):
|
|
"""Test extraction of JSON that has comments (invalid JSON)."""
|
|
text = """
|
|
```
|
|
{
|
|
"name": "Test", // This is a comment
|
|
"description": "Testing with comments"
|
|
/*
|
|
Multi-line comment
|
|
*/
|
|
}
|
|
```
|
|
"""
|
|
result = extract_json_from_codeblock(text)
|
|
# Comments would make this invalid JSON
|
|
with pytest.raises(json.JSONDecodeError):
|
|
json.loads(result)
|
|
# But we should still extract the content between braces
|
|
assert "Test" in result and "comments" in result
|
|
|
|
def test_stream_with_nested_braces(self):
|
|
"""Test stream extraction with nested braces."""
|
|
chunks = [
|
|
'{"outer": {',
|
|
'"inner1": {"a": 1},',
|
|
'"inner2": {',
|
|
'"b": 2, "c": {"d": 3}',
|
|
"}",
|
|
"}}",
|
|
]
|
|
|
|
collected = "".join(extract_json_from_stream(chunks))
|
|
parsed = json.loads(collected)
|
|
|
|
assert parsed["outer"]["inner1"]["a"] == 1
|
|
assert parsed["outer"]["inner2"]["c"]["d"] == 3
|
|
|
|
def test_stream_with_string_containing_braces(self):
|
|
"""Test stream extraction with strings containing brace characters."""
|
|
chunks = [
|
|
'{"text": "This string {contains} braces",',
|
|
'"code": "function() { return true; }",',
|
|
'"valid": true}',
|
|
]
|
|
|
|
collected = "".join(extract_json_from_stream(chunks))
|
|
parsed = json.loads(collected)
|
|
|
|
assert parsed["text"] == "This string {contains} braces"
|
|
assert parsed["code"] == "function() { return true; }"
|
|
assert parsed["valid"] is True
|