Skip to content

Split persistence and report JSON serialization (+introduce Pydantic) - #171

Merged
Gerrrr merged 5 commits into
masterfrom
pydantic-and-split-persistence-report-json
Aug 24, 2026
Merged

Split persistence and report JSON serialization (+introduce Pydantic)#171
Gerrrr merged 5 commits into
masterfrom
pydantic-and-split-persistence-report-json

Conversation

@Gerrrr

@Gerrrr Gerrrr commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

This PR separates durable JSON serialization from report formatting (a follow-up to #161) and starts the Pydantic migration from #78.

The first commit makes to_json() mean “precise persistence JSON” for the change-point domain objects. Numeric values now stay numeric, qhat is included in serialized change points, and the old rounded=True/False switch is removed from the domain model. Rounded, human-facing JSON output now belongs to Report, alongside the existing log and regressions-only renderers.

The second commit adds a real JSON boundary test for analyzed-series persistence. It verifies that AnalyzedSeries.to_json() survives json.dumps() / json.loads() before being passed back into AnalyzedSeries.from_json().

The third commit converts AnalysisOptions to a Pydantic v2 model. Defaults now live on the model, unknown fields are rejected, assignment is validated, and AnalyzedSeries.from_json() restores options with AnalysisOptions.model_validate(...) instead of manual field copying. This follows the direction of the closed #93, but keeps the scope tighter and uses Pydantic v2 APIs.

@Gerrrr
Gerrrr marked this pull request as ready for review August 19, 2026 03:15
@Gerrrr
Gerrrr requested review from henrikingo and a lite review from Copilot August 19, 2026 03:17

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR separates durable persistence JSON from human-facing report JSON, and begins migrating JSON boundaries to Pydantic v2 models (notably AnalysisOptions and AnalyzedSeries).

Changes:

  • Make change-point domain to_json() output precise persistence JSON (numeric types preserved, qhat included) and remove the old rounded= switch.
  • Move rounded/stringified JSON formatting into Report’s JSON renderer.
  • Introduce Pydantic v2 models for serialization (AnalysisOptionsModel, AnalyzedSeriesModel) and add tests for JSON-boundary round-trips and validation behavior.

Reviewed changes

Copilot reviewed 8 out of 9 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
uv.lock Adds Pydantic and its transitive dependencies to the locked environment.
pyproject.toml Declares pydantic>=2,<3 as a runtime dependency.
otava/serialization.py Introduces Pydantic v2 models that define/validate persistence JSON shapes.
otava/series.py Migrates AnalysisOptions to Pydantic, updates analyzed-series persistence JSON (typed metrics/data/options), and restores options via model validation.
otava/change_point_divisive/base.py Removes rounded JSON paths, makes change-point JSON precise, and includes qhat in serialized change points.
otava/report.py Implements report-specific rounded/stringified JSON rendering for human output.
tests/change_point_classes_test.py Updates expectations to match precise persistence JSON and qhat inclusion.
tests/series_test.py Adds JSON-boundary and Pydantic validation tests for options/analyzed-series persistence.
tests/report_test.py Updates expected report JSON shape/format and refactors some test construction.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread otava/report.py
Comment thread otava/series.py
@Gerrrr
Gerrrr force-pushed the pydantic-and-split-persistence-report-json branch from 4579c54 to 21edff8 Compare August 19, 2026 18:37
@Gerrrr
Gerrrr requested a balanced review from Copilot August 20, 2026 00:30

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 9 changed files in this pull request and generated no new comments.

Suppressed comments (4)

otava/serialization.py:67

  • Top-level series attributes are also not guaranteed to be strings: GraphiteImporter stores None for unmatched events, while database importers copy attribute column values without conversion (otava/importer.py:573-575 and 799-801). Pydantic therefore rejects previously JSON-serializable series at this new validation boundary. Model these lists with nullable/JSON-scalar values rather than str only.
    attributes: Dict[str, List[str]]

otava/serialization.py:62

  • Postgres and BigQuery importers explicitly build List[float] timestamps with datetime.timestamp() (otava/importer.py:546-560 and 772-786). Timestamps with microseconds therefore contain a fractional part, which Pydantic cannot validate as int, so AnalyzedSeries.to_json() now raises instead of persisting those series. Preserve both integer-indexed and fractional Unix times.
    time: List[int]

otava/serialization.py:54

  • Change-point group attributes can be None: GraphiteImporter inserts None when no event matches a timestamp (otava/importer.py:169-180), and those values flow into groups via Series.attributes_at(). This strict str type makes AnalyzedSeries.to_json() fail whenever such a timestamp has a change point. Allow nullable/JSON-scalar attribute values so valid importer output remains persistable.

This issue also appears on line 67 of the same file.

    attributes: Dict[str, str]

otava/series.py:450

  • Neither the structured nor legacy metric branch actually restores unit: Metric.__init__ unconditionally assigns self.unit = "" at otava/series.py:55, ignoring the third argument passed here. Consequently from_json() silently loses any persisted unit and the next to_json() emits an empty value. Update the constructor to retain its unit argument and cover the metric round trip.
                    metric_json.get("unit", ""),
                )
            else:
                new_metrics[metric_name] = Metric(None, None, metric_json)

Gerrrr added 5 commits August 23, 2026 21:25
Make domain to_json() methods return the precise persistence representation:
ChangePoint and ChangePointGroup now emit numeric fields, include qhat, and no
longer use rounded=True/False as a presentation switch.

Move rounded JSON output into Report, alongside the other report renderers.
ReportType.JSON now owns display-only fields such as backward_change_percent and
formats human-facing values as strings.

Add Pydantic v2 schema models for the persisted analyzed-series payload and
validate AnalyzedSeries.to_json() against that schema before returning JSON-safe
data.

Update tests to pin the split: persistence JSON stays numeric and
round-trippable, while report JSON keeps the existing rounded output shape.
Make AnalysisOptions inherit from the Pydantic v2 AnalysisOptionsModel, with defaults defined on the model itself.

Reject unknown option fields and validate assignment so deserialized and CLI-populated options use the same typed contract.

Use model_dump(mode="json") when serializing analyzed-series options, and rebuild options in from_json() with AnalysisOptions.model_validate(...) instead of manual field copying.

Keep the scope narrower than PR #93: reuse the Pydantic direction, but avoid its broader unrelated model conversions and old Pydantic v1 APIs.
Parse change point timestamps back to datetime objects when loading analyzed series from JSON-decoded payloads, so runtime state matches direct in-memory round trips. Also replace the ambiguous report JSON float formatter with explicit six-decimal formatting while preserving the existing report representation.
Allow nullable JSON scalar attributes, preserve integer and fractional timestamps, and retain metric units across current and legacy JSON round trips.
@Gerrrr
Gerrrr force-pushed the pydantic-and-split-persistence-report-json branch from 4cefd6b to 73d0fd9 Compare August 24, 2026 04:25
@Gerrrr
Gerrrr merged commit 19882e9 into master Aug 24, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants