Skip to content

Rework serialization and model dumping for v2 models#393

Merged
loriab merged 15 commits intoMolSSI:next2026from
bennybp:next2026
Mar 29, 2026
Merged

Rework serialization and model dumping for v2 models#393
loriab merged 15 commits intoMolSSI:next2026from
bennybp:next2026

Conversation

@bennybp
Copy link
Copy Markdown
Contributor

@bennybp bennybp commented Mar 11, 2026

Description

This reworks how qcelemental v2 models are serialized/dumped to bring them more inline with standard pydantic behavior.

  1. Previously, models had custom options that forced unset fields to be excluded. This was generally ok, but also counterintuitive when some applications did want those default fields for compatibility/provenance reasons. The led to needing custom serializers.

  2. Models had some (buggy) logic for handling serialization to/from aliased fields. This is now handled by pydantic v2 natively. (see model_config options serialize_by_alias and validate_by_alias).

  3. Because of the above, models were not tolerant of None being passed in (Molecule and WavefunctionProperties), even though it was the default value. For example, constructing a Molecule with fragments=None would result in an error.

So this PR removes the customization, makes the models tolerant of None, and updates some of the tests.

The somewhat breaking change: by default now, calling .model_dump() will dump all fields, even unset and None fields. This can be controlled with exclude_unset=True and exclude_none=True (see pydantic docs). This is also true for things like model_dump_json, and pydantic functions like to_jsonable_python).

The dict() function is removed to also bring our terminology in line with pydantic.

Changelog description

Status

  • Code base linted
  • Ready to go

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 11, 2026

Codecov Report

❌ Patch coverage is 97.46835% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 85.42%. Comparing base (8db037c) to head (739cec7).
⚠️ Report is 8 commits behind head on next2026.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

if "v2" in request.node.name:
assert isinstance(water_dimer_minima.model_dump(mode="json")["geometry"], list)
else:
assert isinstance(water_dimer_minima.dict(encoding="json")["geometry"], list)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

might want to add some of these changes to the MIGRATION.rst guide

with warnings.catch_warnings():
warnings.simplefilter("ignore")
mol_dict = water_dimer_minima.dict()
mol_dict = water_dimer_minima.model_dump()
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

depending on final disposition of def dict, may want to add this back with a comment to leave alone for testing dict() operational.

if "v2" in request.node.name:
assert obj.model_dump(exclude_unset=True).keys() == {"scf_one_electron_energy"}
else:
assert obj.dict().keys() == {"scf_one_electron_energy"}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

revisit when AtomicProperties None's decided

assert obj.return_gradient.shape == (4, 3)
assert obj.scf_total_hessian.shape == (12, 12)
assert obj.model_dump().keys() == {"calcinfo_natom", "return_gradient", "scf_total_hessian"}
assert obj.model_dump(exclude_unset=True).keys() == {"calcinfo_natom", "return_gradient", "scf_total_hessian"}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

these three also revisit after AtomicProperties and WavefunctionProperties

if "v2" in anskey:
instance = model(**instance.model_dump())
else:
instance = model(**instance.dict())
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

revisit after def dict()

# UNCOMMENT IF NEEDED FOR UPGRADE REDO??
def dict(self, **kwargs):
warnings.warn("The `dict` method is deprecated; use `model_dump` instead.", DeprecationWarning)
return self.model_dump(**kwargs)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

revisit after def dict() decided

if key not in self.model_fields_set:
continue
# Handle "by_alias" is always true
alias = self.__class__.model_fields[key].alias
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

hooray, I'm glad this custom logic is gone

@loriab
Copy link
Copy Markdown
Collaborator

loriab commented Mar 20, 2026

Notice of bennybp#1

@loriab loriab merged commit d0fd465 into MolSSI:next2026 Mar 29, 2026
29 of 31 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.

2 participants