From 1d817a228f81a6be22cf7a82264f5c8c4a32e9f7 Mon Sep 17 00:00:00 2001 From: pikulev <54dnya@gmail.com> Date: Sun, 15 Jun 2025 02:24:36 +0300 Subject: [PATCH 1/2] fix(model): serialize entries with the actual type --- .../models/extensions/chrome_devtools.py | 7 ++++++- src/hario_core/models/har_1_2.py | 16 ++++++++++++++-- tests/samples.py | 4 ++-- tests/test_har_parser.py | 19 +++++++++++++++++++ 4 files changed, 41 insertions(+), 5 deletions(-) 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"] From 11074c2a23a70a75a17e46bbeacf262c38a040f7 Mon Sep 17 00:00:00 2001 From: pikulev <54dnya@gmail.com> Date: Sun, 15 Jun 2025 02:30:21 +0300 Subject: [PATCH 2/2] chore: v0.4.2 --- docs/changelog.md | 5 ++++- pyproject.toml | 4 ++-- src/hario_core/__init__.py | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) 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