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
2 changes: 1 addition & 1 deletion batchata/core/job_result.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def to_dict(self) -> Dict[str, Any]:
if isinstance(self.parsed_response, dict):
parsed_response = self.parsed_response
elif isinstance(self.parsed_response, BaseModel):
parsed_response = self.parsed_response.model_dump()
parsed_response = self.parsed_response.model_dump(mode='json')
else:
parsed_response = str(self.parsed_response)

Expand Down
4 changes: 2 additions & 2 deletions docs/batchata.html
Original file line number Diff line number Diff line change
Expand Up @@ -3691,7 +3691,7 @@ <h2 id="configuration">Configuration</h2>
</span><span id="JobResult-54"><a href="#JobResult-54"><span class="linenos"> 54</span></a> <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">parsed_response</span><span class="p">,</span> <span class="nb">dict</span><span class="p">):</span>
</span><span id="JobResult-55"><a href="#JobResult-55"><span class="linenos"> 55</span></a> <span class="n">parsed_response</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">parsed_response</span>
</span><span id="JobResult-56"><a href="#JobResult-56"><span class="linenos"> 56</span></a> <span class="k">elif</span> <span class="nb">isinstance</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">parsed_response</span><span class="p">,</span> <span class="n">BaseModel</span><span class="p">):</span>
</span><span id="JobResult-57"><a href="#JobResult-57"><span class="linenos"> 57</span></a> <span class="n">parsed_response</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">parsed_response</span><span class="o">.</span><span class="n">model_dump</span><span class="p">()</span>
</span><span id="JobResult-57"><a href="#JobResult-57"><span class="linenos"> 57</span></a> <span class="n">parsed_response</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">parsed_response</span><span class="o">.</span><span class="n">model_dump</span><span class="p">(</span><span class="n">mode</span><span class="o">=</span><span class="s1">&#39;json&#39;</span><span class="p">)</span>
</span><span id="JobResult-58"><a href="#JobResult-58"><span class="linenos"> 58</span></a> <span class="k">else</span><span class="p">:</span>
</span><span id="JobResult-59"><a href="#JobResult-59"><span class="linenos"> 59</span></a> <span class="n">parsed_response</span> <span class="o">=</span> <span class="nb">str</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">parsed_response</span><span class="p">)</span>
</span><span id="JobResult-60"><a href="#JobResult-60"><span class="linenos"> 60</span></a>
Expand Down Expand Up @@ -3979,7 +3979,7 @@ <h2 id="configuration">Configuration</h2>
</span><span id="JobResult.to_dict-54"><a href="#JobResult.to_dict-54"><span class="linenos">54</span></a> <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">parsed_response</span><span class="p">,</span> <span class="nb">dict</span><span class="p">):</span>
</span><span id="JobResult.to_dict-55"><a href="#JobResult.to_dict-55"><span class="linenos">55</span></a> <span class="n">parsed_response</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">parsed_response</span>
</span><span id="JobResult.to_dict-56"><a href="#JobResult.to_dict-56"><span class="linenos">56</span></a> <span class="k">elif</span> <span class="nb">isinstance</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">parsed_response</span><span class="p">,</span> <span class="n">BaseModel</span><span class="p">):</span>
</span><span id="JobResult.to_dict-57"><a href="#JobResult.to_dict-57"><span class="linenos">57</span></a> <span class="n">parsed_response</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">parsed_response</span><span class="o">.</span><span class="n">model_dump</span><span class="p">()</span>
</span><span id="JobResult.to_dict-57"><a href="#JobResult.to_dict-57"><span class="linenos">57</span></a> <span class="n">parsed_response</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">parsed_response</span><span class="o">.</span><span class="n">model_dump</span><span class="p">(</span><span class="n">mode</span><span class="o">=</span><span class="s1">&#39;json&#39;</span><span class="p">)</span>
</span><span id="JobResult.to_dict-58"><a href="#JobResult.to_dict-58"><span class="linenos">58</span></a> <span class="k">else</span><span class="p">:</span>
</span><span id="JobResult.to_dict-59"><a href="#JobResult.to_dict-59"><span class="linenos">59</span></a> <span class="n">parsed_response</span> <span class="o">=</span> <span class="nb">str</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">parsed_response</span><span class="p">)</span>
</span><span id="JobResult.to_dict-60"><a href="#JobResult.to_dict-60"><span class="linenos">60</span></a>
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "batchata"
version = "0.4.5"
version = "0.4.6"
description = "Unified Python API for AI batch requests with 50% cost savings on OpenAI and Anthropic"
readme = "README.md"
requires-python = ">=3.12"
Expand Down
113 changes: 113 additions & 0 deletions tests/test_job_result_serialization.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
"""Test JobResult serialization with date fields."""

import json
from datetime import date
from pydantic import BaseModel, Field

from batchata.core.job_result import JobResult


class MockPropertyData(BaseModel):
"""Mock Pydantic model with date fields for testing."""
property_name: str = Field(..., description="Property name")
appraisal_date: date = Field(..., description="Date appraisal was performed")
last_sale_date: date | None = Field(None, description="Date of last sale")
appraised_value: float | None = Field(None, description="Appraised value")


def test_job_result_serialization_with_dates():
"""Test that JobResult can serialize Pydantic models with date fields to JSON."""
# Create a mock Pydantic model with date fields
property_data = MockPropertyData(
property_name="Test Property",
appraisal_date=date(2023, 10, 20),
last_sale_date=date(2022, 5, 15),
appraised_value=1500000.0
)

# Create JobResult with the mock data
job_result = JobResult(
job_id="test-job-123",
raw_response="Test raw response",
parsed_response=property_data,
input_tokens=100,
output_tokens=50,
cost_usd=0.15
)

# Test to_dict() method
result_dict = job_result.to_dict()

# Verify the parsed_response contains serialized dates as strings
assert result_dict["parsed_response"]["appraisal_date"] == "2023-10-20"
assert result_dict["parsed_response"]["last_sale_date"] == "2022-05-15"
assert result_dict["parsed_response"]["property_name"] == "Test Property"
assert result_dict["parsed_response"]["appraised_value"] == 1500000.0

# Test that the result can be serialized to JSON (this was failing before the fix)
json_str = json.dumps(result_dict, indent=2)

# Verify we can parse it back
parsed_back = json.loads(json_str)
assert parsed_back["parsed_response"]["appraisal_date"] == "2023-10-20"
assert parsed_back["parsed_response"]["last_sale_date"] == "2022-05-15"

# Test save_to_json method
import tempfile
import os

with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as tmp_file:
tmp_path = tmp_file.name

try:
# This should not raise an exception
job_result.save_to_json(tmp_path)

# Verify the file was created and contains valid JSON
with open(tmp_path, 'r') as f:
saved_data = json.load(f)

assert saved_data["parsed_response"]["appraisal_date"] == "2023-10-20"
assert saved_data["parsed_response"]["last_sale_date"] == "2022-05-15"

finally:
# Clean up
if os.path.exists(tmp_path):
os.unlink(tmp_path)


def test_job_result_serialization_with_dict_response():
"""Test that JobResult handles dict responses correctly (should not change)."""
# Test with dict response (no Pydantic model)
dict_response = {
"property_name": "Test Property",
"appraisal_date": "2023-10-20", # Already a string
"appraised_value": 1500000.0
}

job_result = JobResult(
job_id="test-job-dict",
raw_response="Test raw response",
parsed_response=dict_response,
input_tokens=100,
output_tokens=50,
cost_usd=0.15
)

# Test to_dict() method
result_dict = job_result.to_dict()

# Should be unchanged since it's already a dict
assert result_dict["parsed_response"]["appraisal_date"] == "2023-10-20"
assert result_dict["parsed_response"]["property_name"] == "Test Property"

# Should serialize to JSON without issues
json_str = json.dumps(result_dict, indent=2)
parsed_back = json.loads(json_str)
assert parsed_back["parsed_response"]["appraisal_date"] == "2023-10-20"


if __name__ == "__main__":
test_job_result_serialization_with_dates()
test_job_result_serialization_with_dict_response()
print("All tests passed!")
Loading