-
Notifications
You must be signed in to change notification settings - Fork 3
Add logging for deserialization errors #158
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
f044219
5e6fc1e
fa908fb
48f270e
ffa0dc3
687d0f9
9722e89
632c144
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,5 @@ | ||
| from datetime import datetime, timezone | ||
| from logging import LogRecord | ||
|
|
||
| import pytest | ||
| from inline_snapshot import snapshot | ||
|
|
@@ -47,6 +48,27 @@ class Order(BaseModel): | |
| TEST_KEY_2: str = "test_key_2" | ||
|
|
||
|
|
||
| def model_type_from_log_record(record: LogRecord) -> str: | ||
| if not hasattr(record, "model_type"): | ||
| msg = "Log record does not have a model_type attribute" | ||
| raise ValueError(msg) | ||
| return record.model_type # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType, reportAttributeAccessIssue] | ||
|
|
||
|
|
||
| def error_from_log_record(record: LogRecord) -> str: | ||
| if not hasattr(record, "error"): | ||
| msg = "Log record does not have an error attribute" | ||
| raise ValueError(msg) | ||
| return record.error # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType, reportAttributeAccessIssue] | ||
|
|
||
|
|
||
| def errors_from_log_record(record: LogRecord) -> list[str]: | ||
| if not hasattr(record, "errors"): | ||
| msg = "Log record does not have an errors attribute" | ||
| raise ValueError(msg) | ||
| return record.errors # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType, reportAttributeAccessIssue] | ||
|
|
||
|
|
||
| class TestPydanticAdapter: | ||
| @pytest.fixture | ||
| async def store(self) -> MemoryStore: | ||
|
|
@@ -145,3 +167,53 @@ async def test_complex_adapter_with_list(self, product_list_adapter: PydanticAda | |
|
|
||
| assert await product_list_adapter.delete(collection=TEST_COLLECTION, key=TEST_KEY) | ||
| assert await product_list_adapter.get(collection=TEST_COLLECTION, key=TEST_KEY) is None | ||
|
|
||
| async def test_validation_error_logging( | ||
| self, user_adapter: PydanticAdapter[User], updated_user_adapter: PydanticAdapter[UpdatedUser], caplog: pytest.LogCaptureFixture | ||
| ): | ||
| """Test that validation errors are logged when raise_on_validation_error=False.""" | ||
| import logging | ||
|
|
||
| # Store a User, then try to retrieve as UpdatedUser (missing is_admin field) | ||
| await user_adapter.put(collection=TEST_COLLECTION, key=TEST_KEY, value=SAMPLE_USER) | ||
|
|
||
| with caplog.at_level(logging.ERROR): | ||
| updated_user = await updated_user_adapter.get(collection=TEST_COLLECTION, key=TEST_KEY) | ||
|
|
||
| # Should return None due to validation failure | ||
| assert updated_user is None | ||
|
|
||
| # Check that an error was logged | ||
| assert len(caplog.records) == 1 | ||
| record = caplog.records[0] | ||
| assert record.levelname == "ERROR" | ||
| assert "Validation failed" in record.message | ||
| assert model_type_from_log_record(record) == "Pydantic model" | ||
|
|
||
| errors = errors_from_log_record(record) | ||
| assert len(errors) == 1 | ||
| assert "is_admin" in str(errors[0]) | ||
|
|
||
| async def test_list_validation_error_logging( | ||
| self, product_list_adapter: PydanticAdapter[list[Product]], store: MemoryStore, caplog: pytest.LogCaptureFixture | ||
| ): | ||
| """Test that missing 'items' wrapper is logged for list models.""" | ||
| import logging | ||
|
|
||
| # Manually store invalid data (missing 'items' wrapper) | ||
| await store.put(collection=TEST_COLLECTION, key=TEST_KEY, value={"invalid": "data"}) | ||
|
|
||
| with caplog.at_level(logging.ERROR): | ||
| result = await product_list_adapter.get(collection=TEST_COLLECTION, key=TEST_KEY) | ||
|
|
||
| # Should return None due to missing 'items' wrapper | ||
| assert result is None | ||
|
|
||
| # Check that an error was logged | ||
| assert len(caplog.records) == 1 | ||
| record = caplog.records[0] | ||
| assert record.levelname == "ERROR" | ||
| assert "Missing 'items' wrapper" in record.message | ||
| assert model_type_from_log_record(record) == "Pydantic model" | ||
| error = error_from_log_record(record) | ||
| assert "missing 'items' wrapper" in str(error) | ||
|
Comment on lines
+197
to
+219
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Well-designed test for list validation error logging. The test properly validates that missing 'items' wrapper errors are logged correctly. The use of Same optional suggestion as the previous test: consider moving the import pytest
from inline_snapshot import snapshot
from key_value.shared.errors import DeserializationError
from pydantic import AnyHttpUrl, BaseModel
+import logging
from key_value.aio.adapters.pydantic import PydanticAdapterThen remove the inline import: async def test_list_validation_error_logging(
self, product_list_adapter: PydanticAdapter[list[Product]], store: MemoryStore, caplog: pytest.LogCaptureFixture
):
"""Test that missing 'items' wrapper is logged for list models."""
- import logging
# Manually store invalid data (missing 'items' wrapper)
🤖 Prompt for AI Agents |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Comprehensive test for validation error logging.
The test correctly verifies that validation failures are logged at ERROR level with appropriate context. The test logic is sound, and assertions properly validate the logging behavior.
Consider moving the
loggingimport (line 175) to the top of the file with other imports for consistency, though the current placement is acceptable:import pytest from inline_snapshot import snapshot from key_value.shared.errors import DeserializationError from pydantic import AnyHttpUrl, BaseModel +import logging from key_value.aio.adapters.pydantic import PydanticAdapterThen remove the inline import:
async def test_validation_error_logging( self, user_adapter: PydanticAdapter[User], updated_user_adapter: PydanticAdapter[UpdatedUser], caplog: pytest.LogCaptureFixture ): """Test that validation errors are logged when raise_on_validation_error=False.""" - import logging # Store a User, then try to retrieve as UpdatedUser (missing is_admin field)🤖 Prompt for AI Agents