diff --git a/docs/changelog.md b/docs/changelog.md index 58dda54..d109857 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,5 +1,8 @@ # Changelog +### v0.4.2 +- FIX: Fixed serialization of extended fields (e.g., DevTools) in HarLog dump. Now all additional fields are correctly preserved when calling model_dump(). + ### v0.4.1 - FIX: Removed the mistakenly added `rich` dependency from the requirements. @@ -44,4 +47,4 @@ ## v0.1.0 -- Initial release of `hario-core`. \ No newline at end of file +- Initial release of `hario-core`. diff --git a/pyproject.toml b/pyproject.toml index e86db14..41454e4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api" [project] name = "hario-core" -version = "0.4.1" +version = "0.4.2" description = "Modern, type-safe, and extensible library for parsing, transforming, and analyzing HAR (HTTP Archive) files." authors = [{name = "Vasiliy Pikulev", email = "pikulev.vasiliy@gmail.com"}] readme = "README.md" @@ -106,7 +106,7 @@ convention = "google" [tool.poetry] name = "hario-core" -version = "0.4.1" +version = "0.4.2" description = "Modern, type-safe, and extensible library for parsing, transforming, and analyzing HAR (HTTP Archive) files." authors = ["Vasiliy Pikulev "] readme = "README.md" diff --git a/src/hario_core/__init__.py b/src/hario_core/__init__.py index 9fbca5a..606f5b5 100644 --- a/src/hario_core/__init__.py +++ b/src/hario_core/__init__.py @@ -2,7 +2,7 @@ Hario Core package root. """ -__version__ = "0.4.1" +__version__ = "0.4.2" from . import models, parse, transform diff --git a/src/hario_core/models/extensions/chrome_devtools.py b/src/hario_core/models/extensions/chrome_devtools.py index 67e3a0b..a70edb0 100644 --- a/src/hario_core/models/extensions/chrome_devtools.py +++ b/src/hario_core/models/extensions/chrome_devtools.py @@ -4,7 +4,7 @@ from typing import List, Optional -from pydantic import BaseModel, Field +from pydantic import BaseModel, ConfigDict, Field from ..har_1_2 import Entry, Request, Response, Timings @@ -68,6 +68,11 @@ class DevToolsWebSocketMessage(BaseModel): class DevToolsEntry(Entry): """HAR Entry object with DevTools extensions.""" + model_config = ConfigDict( + extra="allow", + populate_by_name=True, + ) + initiator: Optional[DevToolsInitiator] = Field(None, alias="_initiator") priority: Optional[str] = Field(None, alias="_priority") resourceType: str = Field(alias="_resourceType") diff --git a/src/hario_core/models/har_1_2.py b/src/hario_core/models/har_1_2.py index 88b1309..5145ae5 100644 --- a/src/hario_core/models/har_1_2.py +++ b/src/hario_core/models/har_1_2.py @@ -98,7 +98,8 @@ class Entry(BaseModel): """HAR Entry object.""" model_config = ConfigDict( - extra="forbid", + extra="allow", + populate_by_name=True, ) pageref: Optional[str] = None @@ -135,10 +136,21 @@ class Page(BaseModel): class HarLog(BaseModel): - model_config = ConfigDict(extra="forbid") # strict: forbid vendor‑specific fields + model_config = ConfigDict( + extra="allow", + populate_by_name=True, + ) version: str creator: Creator browser: Optional[Browser] = None pages: List[Page] = [] entries: List[Entry] + + def model_dump(self: HarLog, **kwargs: Any) -> Dict[str, Any]: + dump = super().model_dump(**kwargs) + # manually serialize entries with the actual type + dump["entries"] = [ + entry.model_dump(**kwargs) for entry in self.entries if entry is not None + ] + return dump diff --git a/tests/samples.py b/tests/samples.py index 5a40029..96b9c5b 100644 --- a/tests/samples.py +++ b/tests/samples.py @@ -156,8 +156,6 @@ } } -CHROME_DEVTOOLS_HAR_BYTES: bytes = orjson.dumps(CHROME_DEVTOOLS_HAR) - # Valid HAR 1.2 CLEANED_HAR: Dict[str, Any] = { "log": { @@ -274,6 +272,8 @@ CLEANED_HAR_BYTES: bytes = orjson.dumps(CLEANED_HAR) +CHROME_DEVTOOLS_HAR_BYTES: bytes = orjson.dumps(CHROME_DEVTOOLS_HAR) + # Edge-case: HAR without log field (based on real HAR) INVALID_HAR_NO_LOG: Dict[str, Any] = { k: v for k, v in CHROME_DEVTOOLS_HAR.items() if k != "log" diff --git a/tests/test_har_parser.py b/tests/test_har_parser.py index 82c45c8..b1abb4d 100644 --- a/tests/test_har_parser.py +++ b/tests/test_har_parser.py @@ -240,3 +240,22 @@ def test_validate_with_invalid_entries(self, cleaned_har: Dict[str, Any]) -> Non har["log"]["entries"] = dict() with pytest.raises(ValueError): validate(har) + + def test_har_log_model_dump_preserves_extension_fields(self) -> None: + """ + Test that har_log.model_dump() preserves + Chrome DevTools extension fields at all levels. + """ + har_log = parse(CHROME_DEVTOOLS_HAR_BYTES) + dumped = har_log.model_dump() + # Check that entries exists and is not empty + assert "entries" in dumped + assert len(dumped["entries"]) > 0 + entry_dump = dumped["entries"][0] + # Check that DevTools extension fields are preserved + assert "initiator" in entry_dump + assert "connectionId" in entry_dump + # Check that nested fields are preserved + assert isinstance(entry_dump["initiator"], dict) + assert entry_dump["initiator"]["type"] == "parser" + assert "transferSize" in entry_dump["response"]