From 0e3567f090f141d4a70ec7cf62f9dcaec42d3ae1 Mon Sep 17 00:00:00 2001 From: Colton Hicks Date: Wed, 30 Aug 2023 19:15:53 -0700 Subject: [PATCH] Updated pydantic v1 -> v2. --- CHANGELOG.md | 4 + poetry.lock | 242 +++++++++++++++++++++++------------- pyproject.toml | 4 +- qcio/helper_types.py | 14 ++- qcio/models/base_models.py | 115 +++++++---------- qcio/models/inputs_base.py | 7 +- qcio/models/molecule.py | 19 ++- qcio/models/outputs.py | 15 +-- qcio/qcel.py | 7 +- tests/test_base_io.py | 6 +- tests/test_molecule.py | 11 +- tests/test_serialization.py | 15 +-- tests/test_single_point.py | 11 +- 13 files changed, 264 insertions(+), 206 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc47e8f..0376771 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [unreleased] +### Changed + +- 🚨 BREAKING CHANGE 🚨 Updated `pydantic` from `v1` -> `v2`. + ## [0.4.2] ### Added diff --git a/poetry.lock b/poetry.lock index ce2798f..c7b1a0d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,10 +1,23 @@ -# This file is automatically @generated by Poetry and should not be changed by hand. +# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. + +[[package]] +name = "annotated-types" +version = "0.5.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.7" +files = [ + {file = "annotated_types-0.5.0-py3-none-any.whl", hash = "sha256:58da39888f92c276ad970249761ebea80ba544b77acddaa1a4d6cf78287d45fd"}, + {file = "annotated_types-0.5.0.tar.gz", hash = "sha256:47cdc3490d9ac1506ce92c7aaa76c579dc3509ff11e098fc867e5130ab7be802"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.9\""} [[package]] name = "black" version = "23.7.0" description = "The uncompromising code formatter." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -51,7 +64,6 @@ uvloop = ["uvloop (>=0.15.2)"] name = "cfgv" version = "3.3.1" description = "Validate configuration and produce human readable error messages." -category = "dev" optional = false python-versions = ">=3.6.1" files = [ @@ -63,7 +75,6 @@ files = [ name = "click" version = "8.1.5" description = "Composable command line interface toolkit" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -78,7 +89,6 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." -category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ @@ -90,7 +100,6 @@ files = [ name = "coverage" version = "7.2.7" description = "Code coverage measurement for Python" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -166,7 +175,6 @@ toml = ["tomli"] name = "distlib" version = "0.3.7" description = "Distribution utilities" -category = "dev" optional = false python-versions = "*" files = [ @@ -178,7 +186,6 @@ files = [ name = "exceptiongroup" version = "1.1.2" description = "Backport of PEP 654 (exception groups)" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -193,7 +200,6 @@ test = ["pytest (>=6)"] name = "filelock" version = "3.12.2" description = "A platform independent file lock." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -209,7 +215,6 @@ testing = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "diff-cover (>=7.5)", "p name = "identify" version = "2.5.24" description = "File identification library for Python" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -224,7 +229,6 @@ license = ["ukkonen"] name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -236,7 +240,6 @@ files = [ name = "isort" version = "5.12.0" description = "A Python utility / library to sort Python imports." -category = "dev" optional = false python-versions = ">=3.8.0" files = [ @@ -254,7 +257,6 @@ requirements-deprecated-finder = ["pip-api", "pipreqs"] name = "mypy" version = "1.4.1" description = "Optional static typing for Python" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -301,7 +303,6 @@ reports = ["lxml"] name = "mypy-extensions" version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -313,7 +314,6 @@ files = [ name = "nodeenv" version = "1.8.0" description = "Node.js virtual environment builder" -category = "dev" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" files = [ @@ -328,7 +328,6 @@ setuptools = "*" name = "numpy" version = "1.24.4" description = "Fundamental package for array computing in Python" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -366,7 +365,6 @@ files = [ name = "packaging" version = "23.1" description = "Core utilities for Python packages" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -378,7 +376,6 @@ files = [ name = "pathspec" version = "0.11.1" description = "Utility library for gitignore style pattern matching of file paths." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -390,7 +387,6 @@ files = [ name = "pint" version = "0.21.1" description = "Physical quantities module" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -412,7 +408,6 @@ xarray = ["xarray"] name = "platformdirs" version = "3.9.1" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -428,7 +423,6 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest- name = "pluggy" version = "1.2.0" description = "plugin and hook calling mechanisms for python" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -444,7 +438,6 @@ testing = ["pytest", "pytest-benchmark"] name = "pre-commit" version = "3.3.3" description = "A framework for managing and maintaining multi-language pre-commit hooks." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -461,62 +454,145 @@ virtualenv = ">=20.10.0" [[package]] name = "pydantic" -version = "1.10.11" -description = "Data validation and settings management using python type hints" -category = "main" +version = "2.3.0" +description = "Data validation using Python type hints" optional = false python-versions = ">=3.7" files = [ - {file = "pydantic-1.10.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ff44c5e89315b15ff1f7fdaf9853770b810936d6b01a7bcecaa227d2f8fe444f"}, - {file = "pydantic-1.10.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a6c098d4ab5e2d5b3984d3cb2527e2d6099d3de85630c8934efcfdc348a9760e"}, - {file = "pydantic-1.10.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16928fdc9cb273c6af00d9d5045434c39afba5f42325fb990add2c241402d151"}, - {file = "pydantic-1.10.11-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0588788a9a85f3e5e9ebca14211a496409cb3deca5b6971ff37c556d581854e7"}, - {file = "pydantic-1.10.11-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e9baf78b31da2dc3d3f346ef18e58ec5f12f5aaa17ac517e2ffd026a92a87588"}, - {file = "pydantic-1.10.11-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:373c0840f5c2b5b1ccadd9286782852b901055998136287828731868027a724f"}, - {file = "pydantic-1.10.11-cp310-cp310-win_amd64.whl", hash = "sha256:c3339a46bbe6013ef7bdd2844679bfe500347ac5742cd4019a88312aa58a9847"}, - {file = "pydantic-1.10.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:08a6c32e1c3809fbc49debb96bf833164f3438b3696abf0fbeceb417d123e6eb"}, - {file = "pydantic-1.10.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a451ccab49971af043ec4e0d207cbc8cbe53dbf148ef9f19599024076fe9c25b"}, - {file = "pydantic-1.10.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b02d24f7b2b365fed586ed73582c20f353a4c50e4be9ba2c57ab96f8091ddae"}, - {file = "pydantic-1.10.11-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f34739a89260dfa420aa3cbd069fbcc794b25bbe5c0a214f8fb29e363484b66"}, - {file = "pydantic-1.10.11-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e297897eb4bebde985f72a46a7552a7556a3dd11e7f76acda0c1093e3dbcf216"}, - {file = "pydantic-1.10.11-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d185819a7a059550ecb85d5134e7d40f2565f3dd94cfd870132c5f91a89cf58c"}, - {file = "pydantic-1.10.11-cp311-cp311-win_amd64.whl", hash = "sha256:4400015f15c9b464c9db2d5d951b6a780102cfa5870f2c036d37c23b56f7fc1b"}, - {file = "pydantic-1.10.11-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2417de68290434461a266271fc57274a138510dca19982336639484c73a07af6"}, - {file = "pydantic-1.10.11-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:331c031ba1554b974c98679bd0780d89670d6fd6f53f5d70b10bdc9addee1713"}, - {file = "pydantic-1.10.11-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8268a735a14c308923e8958363e3a3404f6834bb98c11f5ab43251a4e410170c"}, - {file = "pydantic-1.10.11-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:44e51ba599c3ef227e168424e220cd3e544288c57829520dc90ea9cb190c3248"}, - {file = "pydantic-1.10.11-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d7781f1d13b19700b7949c5a639c764a077cbbdd4322ed505b449d3ca8edcb36"}, - {file = "pydantic-1.10.11-cp37-cp37m-win_amd64.whl", hash = "sha256:7522a7666157aa22b812ce14c827574ddccc94f361237ca6ea8bb0d5c38f1629"}, - {file = "pydantic-1.10.11-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bc64eab9b19cd794a380179ac0e6752335e9555d214cfcb755820333c0784cb3"}, - {file = "pydantic-1.10.11-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8dc77064471780262b6a68fe67e013298d130414d5aaf9b562c33987dbd2cf4f"}, - {file = "pydantic-1.10.11-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe429898f2c9dd209bd0632a606bddc06f8bce081bbd03d1c775a45886e2c1cb"}, - {file = "pydantic-1.10.11-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:192c608ad002a748e4a0bed2ddbcd98f9b56df50a7c24d9a931a8c5dd053bd3d"}, - {file = "pydantic-1.10.11-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ef55392ec4bb5721f4ded1096241e4b7151ba6d50a50a80a2526c854f42e6a2f"}, - {file = "pydantic-1.10.11-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:41e0bb6efe86281623abbeeb0be64eab740c865388ee934cd3e6a358784aca6e"}, - {file = "pydantic-1.10.11-cp38-cp38-win_amd64.whl", hash = "sha256:265a60da42f9f27e0b1014eab8acd3e53bd0bad5c5b4884e98a55f8f596b2c19"}, - {file = "pydantic-1.10.11-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:469adf96c8e2c2bbfa655fc7735a2a82f4c543d9fee97bd113a7fb509bf5e622"}, - {file = "pydantic-1.10.11-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e6cbfbd010b14c8a905a7b10f9fe090068d1744d46f9e0c021db28daeb8b6de1"}, - {file = "pydantic-1.10.11-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abade85268cc92dff86d6effcd917893130f0ff516f3d637f50dadc22ae93999"}, - {file = "pydantic-1.10.11-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e9738b0f2e6c70f44ee0de53f2089d6002b10c33264abee07bdb5c7f03038303"}, - {file = "pydantic-1.10.11-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:787cf23e5a0cde753f2eabac1b2e73ae3844eb873fd1f5bdbff3048d8dbb7604"}, - {file = "pydantic-1.10.11-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:174899023337b9fc685ac8adaa7b047050616136ccd30e9070627c1aaab53a13"}, - {file = "pydantic-1.10.11-cp39-cp39-win_amd64.whl", hash = "sha256:1954f8778489a04b245a1e7b8b22a9d3ea8ef49337285693cf6959e4b757535e"}, - {file = "pydantic-1.10.11-py3-none-any.whl", hash = "sha256:008c5e266c8aada206d0627a011504e14268a62091450210eda7c07fabe6963e"}, - {file = "pydantic-1.10.11.tar.gz", hash = "sha256:f66d479cf7eb331372c470614be6511eae96f1f120344c25f3f9bb59fb1b5528"}, + {file = "pydantic-2.3.0-py3-none-any.whl", hash = "sha256:45b5e446c6dfaad9444819a293b921a40e1db1aa61ea08aede0522529ce90e81"}, + {file = "pydantic-2.3.0.tar.gz", hash = "sha256:1607cc106602284cd4a00882986570472f193fde9cb1259bceeaedb26aa79a6d"}, ] [package.dependencies] -typing-extensions = ">=4.2.0" +annotated-types = ">=0.4.0" +pydantic-core = "2.6.3" +typing-extensions = ">=4.6.1" [package.extras] -dotenv = ["python-dotenv (>=0.10.4)"] -email = ["email-validator (>=1.0.3)"] +email = ["email-validator (>=2.0.0)"] + +[[package]] +name = "pydantic-core" +version = "2.6.3" +description = "" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pydantic_core-2.6.3-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:1a0ddaa723c48af27d19f27f1c73bdc615c73686d763388c8683fe34ae777bad"}, + {file = "pydantic_core-2.6.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5cfde4fab34dd1e3a3f7f3db38182ab6c95e4ea91cf322242ee0be5c2f7e3d2f"}, + {file = "pydantic_core-2.6.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5493a7027bfc6b108e17c3383959485087d5942e87eb62bbac69829eae9bc1f7"}, + {file = "pydantic_core-2.6.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:84e87c16f582f5c753b7f39a71bd6647255512191be2d2dbf49458c4ef024588"}, + {file = "pydantic_core-2.6.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:522a9c4a4d1924facce7270c84b5134c5cabcb01513213662a2e89cf28c1d309"}, + {file = "pydantic_core-2.6.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aaafc776e5edc72b3cad1ccedb5fd869cc5c9a591f1213aa9eba31a781be9ac1"}, + {file = "pydantic_core-2.6.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a750a83b2728299ca12e003d73d1264ad0440f60f4fc9cee54acc489249b728"}, + {file = "pydantic_core-2.6.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9e8b374ef41ad5c461efb7a140ce4730661aadf85958b5c6a3e9cf4e040ff4bb"}, + {file = "pydantic_core-2.6.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b594b64e8568cf09ee5c9501ede37066b9fc41d83d58f55b9952e32141256acd"}, + {file = "pydantic_core-2.6.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2a20c533cb80466c1d42a43a4521669ccad7cf2967830ac62c2c2f9cece63e7e"}, + {file = "pydantic_core-2.6.3-cp310-none-win32.whl", hash = "sha256:04fe5c0a43dec39aedba0ec9579001061d4653a9b53a1366b113aca4a3c05ca7"}, + {file = "pydantic_core-2.6.3-cp310-none-win_amd64.whl", hash = "sha256:6bf7d610ac8f0065a286002a23bcce241ea8248c71988bda538edcc90e0c39ad"}, + {file = "pydantic_core-2.6.3-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:6bcc1ad776fffe25ea5c187a028991c031a00ff92d012ca1cc4714087e575973"}, + {file = "pydantic_core-2.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:df14f6332834444b4a37685810216cc8fe1fe91f447332cd56294c984ecbff1c"}, + {file = "pydantic_core-2.6.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0b7486d85293f7f0bbc39b34e1d8aa26210b450bbd3d245ec3d732864009819"}, + {file = "pydantic_core-2.6.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a892b5b1871b301ce20d40b037ffbe33d1407a39639c2b05356acfef5536d26a"}, + {file = "pydantic_core-2.6.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:883daa467865e5766931e07eb20f3e8152324f0adf52658f4d302242c12e2c32"}, + {file = "pydantic_core-2.6.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4eb77df2964b64ba190eee00b2312a1fd7a862af8918ec70fc2d6308f76ac64"}, + {file = "pydantic_core-2.6.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ce8c84051fa292a5dc54018a40e2a1926fd17980a9422c973e3ebea017aa8da"}, + {file = "pydantic_core-2.6.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:22134a4453bd59b7d1e895c455fe277af9d9d9fbbcb9dc3f4a97b8693e7e2c9b"}, + {file = "pydantic_core-2.6.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:02e1c385095efbd997311d85c6021d32369675c09bcbfff3b69d84e59dc103f6"}, + {file = "pydantic_core-2.6.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d79f1f2f7ebdb9b741296b69049ff44aedd95976bfee38eb4848820628a99b50"}, + {file = "pydantic_core-2.6.3-cp311-none-win32.whl", hash = "sha256:430ddd965ffd068dd70ef4e4d74f2c489c3a313adc28e829dd7262cc0d2dd1e8"}, + {file = "pydantic_core-2.6.3-cp311-none-win_amd64.whl", hash = "sha256:84f8bb34fe76c68c9d96b77c60cef093f5e660ef8e43a6cbfcd991017d375950"}, + {file = "pydantic_core-2.6.3-cp311-none-win_arm64.whl", hash = "sha256:5a2a3c9ef904dcdadb550eedf3291ec3f229431b0084666e2c2aa8ff99a103a2"}, + {file = "pydantic_core-2.6.3-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:8421cf496e746cf8d6b677502ed9a0d1e4e956586cd8b221e1312e0841c002d5"}, + {file = "pydantic_core-2.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bb128c30cf1df0ab78166ded1ecf876620fb9aac84d2413e8ea1594b588c735d"}, + {file = "pydantic_core-2.6.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37a822f630712817b6ecc09ccc378192ef5ff12e2c9bae97eb5968a6cdf3b862"}, + {file = "pydantic_core-2.6.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:240a015102a0c0cc8114f1cba6444499a8a4d0333e178bc504a5c2196defd456"}, + {file = "pydantic_core-2.6.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f90e5e3afb11268628c89f378f7a1ea3f2fe502a28af4192e30a6cdea1e7d5e"}, + {file = "pydantic_core-2.6.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:340e96c08de1069f3d022a85c2a8c63529fd88709468373b418f4cf2c949fb0e"}, + {file = "pydantic_core-2.6.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1480fa4682e8202b560dcdc9eeec1005f62a15742b813c88cdc01d44e85308e5"}, + {file = "pydantic_core-2.6.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f14546403c2a1d11a130b537dda28f07eb6c1805a43dae4617448074fd49c282"}, + {file = "pydantic_core-2.6.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a87c54e72aa2ef30189dc74427421e074ab4561cf2bf314589f6af5b37f45e6d"}, + {file = "pydantic_core-2.6.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f93255b3e4d64785554e544c1c76cd32f4a354fa79e2eeca5d16ac2e7fdd57aa"}, + {file = "pydantic_core-2.6.3-cp312-none-win32.whl", hash = "sha256:f70dc00a91311a1aea124e5f64569ea44c011b58433981313202c46bccbec0e1"}, + {file = "pydantic_core-2.6.3-cp312-none-win_amd64.whl", hash = "sha256:23470a23614c701b37252618e7851e595060a96a23016f9a084f3f92f5ed5881"}, + {file = "pydantic_core-2.6.3-cp312-none-win_arm64.whl", hash = "sha256:1ac1750df1b4339b543531ce793b8fd5c16660a95d13aecaab26b44ce11775e9"}, + {file = "pydantic_core-2.6.3-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:a53e3195f134bde03620d87a7e2b2f2046e0e5a8195e66d0f244d6d5b2f6d31b"}, + {file = "pydantic_core-2.6.3-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:f2969e8f72c6236c51f91fbb79c33821d12a811e2a94b7aa59c65f8dbdfad34a"}, + {file = "pydantic_core-2.6.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:672174480a85386dd2e681cadd7d951471ad0bb028ed744c895f11f9d51b9ebe"}, + {file = "pydantic_core-2.6.3-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:002d0ea50e17ed982c2d65b480bd975fc41086a5a2f9c924ef8fc54419d1dea3"}, + {file = "pydantic_core-2.6.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3ccc13afee44b9006a73d2046068d4df96dc5b333bf3509d9a06d1b42db6d8bf"}, + {file = "pydantic_core-2.6.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:439a0de139556745ae53f9cc9668c6c2053444af940d3ef3ecad95b079bc9987"}, + {file = "pydantic_core-2.6.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d63b7545d489422d417a0cae6f9898618669608750fc5e62156957e609e728a5"}, + {file = "pydantic_core-2.6.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b44c42edc07a50a081672e25dfe6022554b47f91e793066a7b601ca290f71e42"}, + {file = "pydantic_core-2.6.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1c721bfc575d57305dd922e6a40a8fe3f762905851d694245807a351ad255c58"}, + {file = "pydantic_core-2.6.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:5e4a2cf8c4543f37f5dc881de6c190de08096c53986381daebb56a355be5dfe6"}, + {file = "pydantic_core-2.6.3-cp37-none-win32.whl", hash = "sha256:d9b4916b21931b08096efed090327f8fe78e09ae8f5ad44e07f5c72a7eedb51b"}, + {file = "pydantic_core-2.6.3-cp37-none-win_amd64.whl", hash = "sha256:a8acc9dedd304da161eb071cc7ff1326aa5b66aadec9622b2574ad3ffe225525"}, + {file = "pydantic_core-2.6.3-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:5e9c068f36b9f396399d43bfb6defd4cc99c36215f6ff33ac8b9c14ba15bdf6b"}, + {file = "pydantic_core-2.6.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e61eae9b31799c32c5f9b7be906be3380e699e74b2db26c227c50a5fc7988698"}, + {file = "pydantic_core-2.6.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d85463560c67fc65cd86153a4975d0b720b6d7725cf7ee0b2d291288433fc21b"}, + {file = "pydantic_core-2.6.3-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9616567800bdc83ce136e5847d41008a1d602213d024207b0ff6cab6753fe645"}, + {file = "pydantic_core-2.6.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e9b65a55bbabda7fccd3500192a79f6e474d8d36e78d1685496aad5f9dbd92c"}, + {file = "pydantic_core-2.6.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f468d520f47807d1eb5d27648393519655eadc578d5dd862d06873cce04c4d1b"}, + {file = "pydantic_core-2.6.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9680dd23055dd874173a3a63a44e7f5a13885a4cfd7e84814be71be24fba83db"}, + {file = "pydantic_core-2.6.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a718d56c4d55efcfc63f680f207c9f19c8376e5a8a67773535e6f7e80e93170"}, + {file = "pydantic_core-2.6.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8ecbac050856eb6c3046dea655b39216597e373aa8e50e134c0e202f9c47efec"}, + {file = "pydantic_core-2.6.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:788be9844a6e5c4612b74512a76b2153f1877cd845410d756841f6c3420230eb"}, + {file = "pydantic_core-2.6.3-cp38-none-win32.whl", hash = "sha256:07a1aec07333bf5adebd8264047d3dc518563d92aca6f2f5b36f505132399efc"}, + {file = "pydantic_core-2.6.3-cp38-none-win_amd64.whl", hash = "sha256:621afe25cc2b3c4ba05fff53525156d5100eb35c6e5a7cf31d66cc9e1963e378"}, + {file = "pydantic_core-2.6.3-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:813aab5bfb19c98ae370952b6f7190f1e28e565909bfc219a0909db168783465"}, + {file = "pydantic_core-2.6.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:50555ba3cb58f9861b7a48c493636b996a617db1a72c18da4d7f16d7b1b9952b"}, + {file = "pydantic_core-2.6.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19e20f8baedd7d987bd3f8005c146e6bcbda7cdeefc36fad50c66adb2dd2da48"}, + {file = "pydantic_core-2.6.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b0a5d7edb76c1c57b95df719af703e796fc8e796447a1da939f97bfa8a918d60"}, + {file = "pydantic_core-2.6.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f06e21ad0b504658a3a9edd3d8530e8cea5723f6ea5d280e8db8efc625b47e49"}, + {file = "pydantic_core-2.6.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea053cefa008fda40f92aab937fb9f183cf8752e41dbc7bc68917884454c6362"}, + {file = "pydantic_core-2.6.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:171a4718860790f66d6c2eda1d95dd1edf64f864d2e9f9115840840cf5b5713f"}, + {file = "pydantic_core-2.6.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5ed7ceca6aba5331ece96c0e328cd52f0dcf942b8895a1ed2642de50800b79d3"}, + {file = "pydantic_core-2.6.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:acafc4368b289a9f291e204d2c4c75908557d4f36bd3ae937914d4529bf62a76"}, + {file = "pydantic_core-2.6.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1aa712ba150d5105814e53cb141412217146fedc22621e9acff9236d77d2a5ef"}, + {file = "pydantic_core-2.6.3-cp39-none-win32.whl", hash = "sha256:44b4f937b992394a2e81a5c5ce716f3dcc1237281e81b80c748b2da6dd5cf29a"}, + {file = "pydantic_core-2.6.3-cp39-none-win_amd64.whl", hash = "sha256:9b33bf9658cb29ac1a517c11e865112316d09687d767d7a0e4a63d5c640d1b17"}, + {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:d7050899026e708fb185e174c63ebc2c4ee7a0c17b0a96ebc50e1f76a231c057"}, + {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:99faba727727b2e59129c59542284efebbddade4f0ae6a29c8b8d3e1f437beb7"}, + {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fa159b902d22b283b680ef52b532b29554ea2a7fc39bf354064751369e9dbd7"}, + {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:046af9cfb5384f3684eeb3f58a48698ddab8dd870b4b3f67f825353a14441418"}, + {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:930bfe73e665ebce3f0da2c6d64455098aaa67e1a00323c74dc752627879fc67"}, + {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:85cc4d105747d2aa3c5cf3e37dac50141bff779545ba59a095f4a96b0a460e70"}, + {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b25afe9d5c4f60dcbbe2b277a79be114e2e65a16598db8abee2a2dcde24f162b"}, + {file = "pydantic_core-2.6.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e49ce7dc9f925e1fb010fc3d555250139df61fa6e5a0a95ce356329602c11ea9"}, + {file = "pydantic_core-2.6.3-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:2dd50d6a1aef0426a1d0199190c6c43ec89812b1f409e7fe44cb0fbf6dfa733c"}, + {file = "pydantic_core-2.6.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6595b0d8c8711e8e1dc389d52648b923b809f68ac1c6f0baa525c6440aa0daa"}, + {file = "pydantic_core-2.6.3-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ef724a059396751aef71e847178d66ad7fc3fc969a1a40c29f5aac1aa5f8784"}, + {file = "pydantic_core-2.6.3-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3c8945a105f1589ce8a693753b908815e0748f6279959a4530f6742e1994dcb6"}, + {file = "pydantic_core-2.6.3-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c8c6660089a25d45333cb9db56bb9e347241a6d7509838dbbd1931d0e19dbc7f"}, + {file = "pydantic_core-2.6.3-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:692b4ff5c4e828a38716cfa92667661a39886e71136c97b7dac26edef18767f7"}, + {file = "pydantic_core-2.6.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:f1a5d8f18877474c80b7711d870db0eeef9442691fcdb00adabfc97e183ee0b0"}, + {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:3796a6152c545339d3b1652183e786df648ecdf7c4f9347e1d30e6750907f5bb"}, + {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:b962700962f6e7a6bd77e5f37320cabac24b4c0f76afeac05e9f93cf0c620014"}, + {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56ea80269077003eaa59723bac1d8bacd2cd15ae30456f2890811efc1e3d4413"}, + {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75c0ebbebae71ed1e385f7dfd9b74c1cff09fed24a6df43d326dd7f12339ec34"}, + {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:252851b38bad3bfda47b104ffd077d4f9604a10cb06fe09d020016a25107bf98"}, + {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:6656a0ae383d8cd7cc94e91de4e526407b3726049ce8d7939049cbfa426518c8"}, + {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:d9140ded382a5b04a1c030b593ed9bf3088243a0a8b7fa9f071a5736498c5483"}, + {file = "pydantic_core-2.6.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:d38bbcef58220f9c81e42c255ef0bf99735d8f11edef69ab0b499da77105158a"}, + {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:c9d469204abcca28926cbc28ce98f28e50e488767b084fb3fbdf21af11d3de26"}, + {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:48c1ed8b02ffea4d5c9c220eda27af02b8149fe58526359b3c07eb391cb353a2"}, + {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b2b1bfed698fa410ab81982f681f5b1996d3d994ae8073286515ac4d165c2e7"}, + {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf9d42a71a4d7a7c1f14f629e5c30eac451a6fc81827d2beefd57d014c006c4a"}, + {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4292ca56751aebbe63a84bbfc3b5717abb09b14d4b4442cc43fd7c49a1529efd"}, + {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:7dc2ce039c7290b4ef64334ec7e6ca6494de6eecc81e21cb4f73b9b39991408c"}, + {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:615a31b1629e12445c0e9fc8339b41aaa6cc60bd53bf802d5fe3d2c0cda2ae8d"}, + {file = "pydantic_core-2.6.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1fa1f6312fb84e8c281f32b39affe81984ccd484da6e9d65b3d18c202c666149"}, + {file = "pydantic_core-2.6.3.tar.gz", hash = "sha256:1508f37ba9e3ddc0189e6ff4e2228bd2d3c3a4641cbe8c07177162f76ed696c7"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" [[package]] name = "pytest" version = "7.4.0" description = "pytest: simple powerful testing with Python" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -539,7 +615,6 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no name = "pytest-cov" version = "4.1.0" description = "Pytest plugin for measuring coverage." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -558,7 +633,6 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtuale name = "pyyaml" version = "6.0" description = "YAML parser and emitter for Python" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -606,33 +680,32 @@ files = [ [[package]] name = "qcelemental" -version = "0.25.1" -description = "Essentials for Quantum Chemistry." -category = "dev" +version = "0.26.0" +description = "Core data structures for Quantum Chemistry." optional = false -python-versions = ">=3.6" +python-versions = ">=3.7,<4.0" files = [ - {file = "qcelemental-0.25.1-py3-none-any.whl", hash = "sha256:c923616ba29b50b9a99ea4fa1b6a1ad26d0f6070fc2b891e6e91f3df1de6d2ae"}, - {file = "qcelemental-0.25.1.tar.gz", hash = "sha256:e3e0e53fe047d14756718441040a5d737135f0bdb33efb1d63a1935b95821a74"}, + {file = "qcelemental-0.26.0-py3-none-any.whl", hash = "sha256:0b2e77e93dc8e14c78e54c2aafa6198661ffb49abae73a1352e5f384609dd5ff"}, + {file = "qcelemental-0.26.0.tar.gz", hash = "sha256:a14e8510cdbfda645ef1461c1afd02efb599311733dbba5f83fcd1d6168a6ddd"}, ] [package.dependencies] -numpy = ">=1.12.0" +numpy = [ + {version = ">=1.12.0", markers = "python_version == \"3.8\""}, + {version = ">=1.24.1", markers = "python_version >= \"3.9\""}, +] pint = ">=0.10.0" pydantic = ">=1.8.2" [package.extras] -align = ["networkx (>=2.4.0)"] -docs = ["autodoc-pydantic", "numpydoc", "sphinx", "sphinx-rtd-theme", "sphinxcontrib-napoleon"] -lint = ["autoflake", "black", "isort"] -tests = ["pytest (>=4.0.0)", "pytest-cov"] -viz = ["nglview"] +align = ["networkx (<3.0)", "scipy (>=1.6.0)", "scipy (>=1.9.0)"] +test = ["pytest (>=7.2.2,<8.0.0)"] +viz = ["ipykernel (<6.0.0)", "nglview (>=3.0.3,<4.0.0)"] [[package]] name = "ruff" version = "0.0.260" description = "An extremely fast Python linter, written in Rust." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -659,7 +732,6 @@ files = [ name = "setuptools" version = "68.0.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -676,7 +748,6 @@ testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs ( name = "toml" version = "0.10.2" description = "Python Library for Tom's Obvious, Minimal Language" -category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -688,7 +759,6 @@ files = [ name = "tomli" version = "2.0.1" description = "A lil' TOML parser" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -700,7 +770,6 @@ files = [ name = "types-pyyaml" version = "6.0.12.10" description = "Typing stubs for PyYAML" -category = "dev" optional = false python-versions = "*" files = [ @@ -712,7 +781,6 @@ files = [ name = "types-toml" version = "0.10.8.6" description = "Typing stubs for toml" -category = "dev" optional = false python-versions = "*" files = [ @@ -724,7 +792,6 @@ files = [ name = "typing-extensions" version = "4.7.1" description = "Backported and Experimental Type Hints for Python 3.7+" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -736,7 +803,6 @@ files = [ name = "virtualenv" version = "20.24.0" description = "Virtual Python Environment builder" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -756,4 +822,4 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "d9f35f316a2ee4d1f285c7eaab13b1951762444bb94d5321260f80c7fd083d32" +content-hash = "92fc36d95a6e694898268e1be3be3e6a89ece3020bacc8e513f3277c3c3eade6" diff --git a/pyproject.toml b/pyproject.toml index 0880ddc..c6cccaf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ readme = "README.md" [tool.poetry.dependencies] python = "^3.8" -pydantic = ">=1.7.4,!=1.8,!=1.8.1,<2.0.0" +pydantic = ">=2.0.0" numpy = ">=1.20" toml = "^0.10.2" pyyaml = "^6.0" @@ -26,7 +26,7 @@ pre-commit = "^3.2.1" pytest-cov = "^4.0.0" ruff = "^0.0.260" isort = "^5.12.0" -qcelemental = "^0.25.1" +qcelemental = ">=0.26.0" types-toml = "^0.10.8.6" types-pyyaml = "^6.0.12.10" diff --git a/qcio/helper_types.py b/qcio/helper_types.py index 0c14c80..7fd95aa 100644 --- a/qcio/helper_types.py +++ b/qcio/helper_types.py @@ -2,14 +2,22 @@ from typing import List, Union import numpy as np +from pydantic import PlainSerializer +from typing_extensions import Annotated -StrOrPath = Union[str, Path] +StrOrPath = Annotated[Union[str, Path], PlainSerializer(lambda x: str(x))] # May be energy (float), gradient or hessian (List[List[float]]) SPReturnResult = Union[float, List[List[float]]] # Type for any values that can be coerced to 2D numpy array -ArrayLike2D = Union[List[List[float]], List[float], np.ndarray] +ArrayLike2D = Annotated[ + Union[List[List[float]], List[float], np.ndarray], + PlainSerializer(lambda x: np.array(x).tolist()), +] # Type for any values that can be coerced to 3D numpy array -ArrayLike3D = Union[List[List[List[float]]], List[List[float]], np.ndarray] +ArrayLike3D = Annotated[ + Union[List[List[List[float]]], List[List[float]], np.ndarray], + PlainSerializer(lambda x: np.array(x).tolist()), +] diff --git a/qcio/models/base_models.py b/qcio/models/base_models.py index 565c5b9..91d3cbc 100644 --- a/qcio/models/base_models.py +++ b/qcio/models/base_models.py @@ -2,14 +2,13 @@ import json from abc import ABC from base64 import b64decode, b64encode -from enum import Enum from pathlib import Path from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union import numpy as np import toml import yaml -from pydantic import BaseModel, Extra, validator +from pydantic import BaseModel, field_serializer, field_validator from typing_extensions import Self from ..helper_types import StrOrPath @@ -35,49 +34,17 @@ class QCIOModelBase(BaseModel, ABC): # version: ClassVar[Literal["v1"]] = "v1" extras: Dict[str, Any] = {} - class Config: + model_config = { # Raises an error if extra fields are passed to model. - extra = Extra.forbid + "extra": "forbid", # Allow numpy types in models. Pydantic will no longer raise an exception for # types it doesn't recognize. # https://docs.pydantic.dev/latest/usage/types/#arbitrary-types-allowed - arbitrary_types_allowed = True + "arbitrary_types_allowed": True, # Don't allow mutation of objects - # https://docs.pydantic.dev/latest/usage/models/#faux-immutability - allow_mutation = False - # convert ndarray to list for JSON serialization - # https://docs.pydantic.dev/latest/usage/exporting_models/#json_encodershttps://docs.pydantic.dev/latest/usage/exporting_models/#json_encoders # noqa: E501 - json_encoders = {np.ndarray: lambda v: v.tolist()} - # exclude fields with value None from serialization - exclude_none = True - - def dict(self, **kwargs): - """Convert the object to a dictionary. - - Properly serialize numpy arrays. Serialization is performed in .dict() so that - multiple string serializers can used it without duplicating logic - (e.g. json, yaml, toml). - """ - model_dict = super().dict(**kwargs) - to_pop = [] - for key, value in model_dict.items(): - # Custom serialization for numpy arrays, enums, and pathlib Paths - if isinstance(value, np.ndarray): - model_dict[key] = value.tolist() - elif issubclass(type(value), Enum): - model_dict[key] = value.value - elif isinstance(value, Path): - model_dict[key] = str(value) - - # Exclude empty lists, dictionaries, and objects with all None values from - # serialization - elif value in [None, [], {}]: - to_pop.append(key) - - for key in to_pop: - model_dict.pop(key) - - return model_dict + # https://docs.pydantic.dev/2.3/api/config/#pydantic.config.ConfigDict.frozen + "frozen": True, + } @classmethod def open(cls, filepath: Union[Path, str]) -> Self: @@ -95,37 +62,42 @@ def open(cls, filepath: Union[Path, str]) -> Self: data = filepath.read_text() if filepath.suffix in [".yaml", ".yml"]: - return cls.parse_obj(yaml.safe_load(data)) + return cls.model_validate(yaml.safe_load(data)) elif filepath.suffix == ".toml": - return cls.parse_obj(toml.loads(data)) + return cls.model_validate(toml.loads(data)) # Assume json for all other file extensions - return cls.parse_raw(data) - # pydantic v2 - # return cls.model_validate_json(filepath.read_text()) + return cls.model_validate_json(data) - def save(self, filepath: Union[Path, str], **kwargs) -> None: - """Write an object to disk as json. + def save( + self, + filepath: Union[Path, str], + exclude_none=True, + **kwargs, + ) -> None: + """Write an object to disk as json, yaml, or toml. Args: - filepath: The path to write the object to.I + filepath: The path to write the object to. + exclude_none: If True, attributes with a value of None will not be written. + Changing default behavior from pydantic.model_dump() to True. """ filepath = Path(filepath) filepath.parent.mkdir(exist_ok=True, parents=True) + model_dict = self.model_dump(mode="json", exclude_none=exclude_none, **kwargs) + if filepath.suffix in [".yaml", ".yml"]: - data = yaml.dump(self.dict(**kwargs)) + data = yaml.dump(model_dict) elif filepath.suffix == ".toml": - data = toml.dumps(self.dict(**kwargs)) + data = toml.dumps(model_dict) else: # Write data to json regardless of file extension - data = self.json(**kwargs) + data = json.dumps(model_dict) filepath.write_text(data) - # pydantic v2 - # filepath.write(self.model_dump()) def __repr_args__(self) -> "ReprArgs": """Only show non empty fields in repr.""" @@ -139,6 +111,15 @@ def exists(value): (name, value) for name, value in self.__dict__.items() if exists(value) ] + def __eq__(self, other: Any) -> bool: + """Check equality of two objects. + + Necessary because BaseModel.__eq__ does not compare numpy arrays. + """ + if isinstance(other, self.__class__): + return self.model_dump() == other.model_dump() + return False + class Files(QCIOModelBase): """File model for handling string and binary data. @@ -151,7 +132,7 @@ class Files(QCIOModelBase): files: Dict[str, Union[str, bytes]] = {} - @validator("files", pre=True) + @field_validator("files") def convert_base64_to_bytes(cls, value): """Convert base64 encoded data to bytes.""" for filename, data in value.items(): @@ -159,21 +140,15 @@ def convert_base64_to_bytes(cls, value): value[filename] = b64decode(data[7:]) return value - def dict(self, *args, **kwargs): - """Return a dict representation of the object encoding bytes as b64 strings.""" - dict = super().dict(*args, **kwargs) - if self.files: # clause so that empty files dict is not included in dict - files = {} - for filename, data in self.files.items(): - if isinstance(data, bytes): - data = f"base64:{b64encode(data).decode('utf-8')}" - files[filename] = data - dict["files"] = files - return dict - - def json(self, *args, **kwargs): - """Return a JSON representation of the object.""" - return json.dumps(self.dict(*args, **kwargs)) + @field_serializer("files") + def serialize_files(self, files, _info) -> Dict[str, str]: + """Serialize files to a dict of filename to base64 encoded string.""" + return { + filename: f"base64:{b64encode(data).decode('utf-8')}" + if isinstance(data, bytes) + else data + for filename, data in files.items() + } def add_file( self, filepath: Union[Path, str], relative_dir: Optional[Path] = None @@ -270,7 +245,7 @@ class Provenance(QCIOModelBase): program: str program_version: Optional[str] = None - scratch_dir: Optional[Path] = None + scratch_dir: Optional[StrOrPath] = None wall_time: Optional[float] = None hostname: Optional[str] = None hostcpus: Optional[int] = None diff --git a/qcio/models/inputs_base.py b/qcio/models/inputs_base.py index 4d4f1f9..a0ce9b5 100644 --- a/qcio/models/inputs_base.py +++ b/qcio/models/inputs_base.py @@ -4,7 +4,7 @@ from enum import Enum from typing import Any, Dict, Optional -from pydantic import BaseModel +from pydantic import BaseModel, field_serializer from .base_models import Files from .molecule import Molecule @@ -103,3 +103,8 @@ class StructuredInputBase(ProgramArgs): calctype: CalcType molecule: Molecule + + @field_serializer("calctype") + def serialize_calctype(self, calctype: CalcType, _info) -> str: + """Serialize CalcType to string""" + return calctype.value diff --git a/qcio/models/molecule.py b/qcio/models/molecule.py index ee21c97..29bc53c 100644 --- a/qcio/models/molecule.py +++ b/qcio/models/molecule.py @@ -3,7 +3,7 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union import numpy as np -from pydantic import validator +from pydantic import field_serializer, field_validator from typing_extensions import Self from qcio.constants import BOHR_TO_ANGSTROM @@ -102,12 +102,21 @@ def __repr_args__(self) -> "ReprArgs": ("formula", self.formula), ] - @validator("geometry") + @field_validator("geometry") def shape_n_by_3(cls, v, values, **kwargs): """Ensure there is an x, y, and z coordinate for each atom.""" - n_atoms = len(values["symbols"]) + n_atoms = len(values.data["symbols"]) return np.array(v).reshape(n_atoms, 3) + @field_serializer("connectivity") + def serialize_connectivity(self, connectivity, _info) -> List[List[float]]: + """Serialize connectivity to a list of tuples. + + Cannot have homogeneous data types in .toml files so must cast all values to + floats. + """ + return [[float(val) for val in bond] for bond in connectivity] + @property def formula(self) -> str: """Return the molecular formula of the molecule using the Hill System. @@ -132,9 +141,9 @@ def formula(self) -> str: for element, count in sorted_elements ) - def dict(self, **kwargs) -> Dict[str, Any]: + def model_dump(self, **kwargs) -> Dict[str, Any]: """Handle tuple in connectivity""" - as_dict = super().dict(**kwargs) + as_dict = super().model_dump(**kwargs) # Connectivity may be empty and super().dict() will remove empty values if (connectivity := as_dict.get("connectivity")) is not None: # Must cast all values to floats as toml cannot handle mixed types diff --git a/qcio/models/outputs.py b/qcio/models/outputs.py index 2cbfa7a..a776cd5 100644 --- a/qcio/models/outputs.py +++ b/qcio/models/outputs.py @@ -2,7 +2,7 @@ from typing import List, Literal, Optional, Union import numpy as np -from pydantic import validator +from pydantic import field_validator from qcio.helper_types import ArrayLike2D, ArrayLike3D @@ -55,13 +55,14 @@ class Wavefunction(QCIOModelBase): scf_occupations_a: Optional[ArrayLike2D] = None scf_occupations_b: Optional[ArrayLike2D] = None - _to_numpy = validator( + @field_validator( "scf_eigenvalues_a", "scf_eigenvalues_b", "scf_occupations_a", "scf_occupations_b", - allow_reuse=True, - )(lambda x: np.asarray(x) if x is not None else None) + ) + def to_numpy(cls, val, _info) -> Optional[np.ndarray]: + return np.asarray(val) if val is not None else None class SinglePointResults(ResultsBase): @@ -112,20 +113,20 @@ class SinglePointResults(ResultsBase): normal_modes_cartesian: Optional[ArrayLike3D] = None gibbs_free_energy: Optional[float] = None - @validator("normal_modes_cartesian") + @field_validator("normal_modes_cartesian") def validate_normal_modes_cartesian_shape(cls, v: ArrayLike3D): if v is not None: # Assume array has length of the number of normal modes n_normal_modes = len(v) return np.asarray(v).reshape(n_normal_modes, -1, 3) - @validator("gradient") + @field_validator("gradient") def validate_gradient_shape(cls, v: ArrayLike2D): """Validate gradient is n x 3""" if v is not None: return np.asarray(v).reshape(-1, 3) - @validator("hessian") + @field_validator("hessian") def validate_hessian_shape(cls, v: ArrayLike2D): """Validate hessian is square""" if v is not None: diff --git a/qcio/qcel.py b/qcio/qcel.py index 83d42a4..5d86ab6 100644 --- a/qcio/qcel.py +++ b/qcio/qcel.py @@ -30,12 +30,13 @@ def to_qcel_input(prog_input: ProgramInput) -> Dict[str, Any]: "connectivity": prog_input.molecule.connectivity or None, "identifiers": { key: value - for key, value in prog_input.molecule.identifiers.dict().items() - if key not in ["name_IUPAC", "name_common"] # not on qcel model + for key, value in prog_input.molecule.identifiers.model_dump().items() + if key + not in ["name_IUPAC", "name_common", "extras"] # not on qcel model }, }, "driver": prog_input.calctype, - "model": prog_input.model.dict(), + "model": prog_input.model.model_dump(), "keywords": prog_input.keywords, "extras": prog_input.extras, } diff --git a/tests/test_base_io.py b/tests/test_base_io.py index 345e97f..d80f093 100644 --- a/tests/test_base_io.py +++ b/tests/test_base_io.py @@ -28,12 +28,10 @@ def test_file_b64(test_data_dir): mixin = Files() mixin.add_file(input_filepath) assert isinstance(mixin.files[input_filepath.name], bytes) - json_str = mixin.json() + json_str = mixin.model_dump_json() json_dict = json.loads(json_str) assert json_dict["files"][input_filepath.name].startswith("base64:") - mixin_new = mixin.parse_raw(json_str) - # v2 - # file = File.model_validate_json(json_dict["data"]) + mixin_new = mixin.model_validate_json(json_str) # Round trip of file is lossless assert mixin_new.files["c0"] == input_filepath.read_bytes() diff --git a/tests/test_molecule.py b/tests/test_molecule.py index 3717ead..72fe8b2 100644 --- a/tests/test_molecule.py +++ b/tests/test_molecule.py @@ -43,16 +43,11 @@ def test_to_from_file_json(test_data_dir, tmp_path): assert caffeine_copy.charge == caffeine.charge -def test_molecule_dict_connectivity(water): +def test_molecule_model_dump_connectivity(water): # Test that connectivity is a list of lists of floats - dict = water.dict() + # Must cast all to the same type as toml cannot handle mixed types + dict = water.model_dump() for bond in dict["connectivity"]: assert isinstance(bond, list) for val in bond: assert isinstance(val, float) - - # Ensure the dict still works if connectivity not included - dict.pop("connectivity") - water = Molecule(**dict) - new_dict = water.dict() - assert "connectivity" not in new_dict diff --git a/tests/test_serialization.py b/tests/test_serialization.py index 11de90f..bd06310 100644 --- a/tests/test_serialization.py +++ b/tests/test_serialization.py @@ -18,16 +18,11 @@ def test_serialization_to_disk_json(sp_output, tmp_path): def test_serialization_to_disk_yaml(sp_output, tmp_path): """Test serialization to disk yaml""" - - filename = tmp_path / "sp_output.yaml" - sp_output.save(filename) - reopened = SinglePointOutput.open(filename) - assert sp_output == reopened - - filename = tmp_path / "sp_output.yml" - sp_output.save(filename) - reopened = SinglePointOutput.open(filename) - assert sp_output == reopened + for ext in [".yaml", ".yml"]: + filename = tmp_path / f"sp_output{ext}" + sp_output.save(filename) + reopened = SinglePointOutput.open(filename) + assert sp_output == reopened def test_serialization_to_disk_toml(sp_output, tmp_path): diff --git a/tests/test_single_point.py b/tests/test_single_point.py index 5b30271..c329abd 100644 --- a/tests/test_single_point.py +++ b/tests/test_single_point.py @@ -122,13 +122,15 @@ def test_return_result(sp_input): assert output.return_result == output.results.energy sp_inp_grad = sp_input("gradient") - output = SinglePointOutput(**{**output.dict(), **{"input_data": sp_inp_grad}}) + output = SinglePointOutput(**{**output.model_dump(), **{"input_data": sp_inp_grad}}) assert np.array_equal(output.return_result, gradient) assert np.array_equal(output.return_result, output.results.gradient) sp_inp_hessian = sp_input("hessian") - output = SinglePointOutput(**{**output.dict(), **{"input_data": sp_inp_hessian}}) + output = SinglePointOutput( + **{**output.model_dump(), **{"input_data": sp_inp_hessian}} + ) assert np.array_equal(output.return_result, hessian) assert np.array_equal(output.return_result, output.results.hessian) @@ -136,9 +138,8 @@ def test_return_result(sp_input): def test_successful_output_serialization(sp_output): """Test that successful result serializes and deserializes""" - serialized = sp_output.json() - # model_validate_json in pydantic v2 - deserialized = SinglePointOutput.parse_raw(serialized) + serialized = sp_output.model_dump_json() + deserialized = SinglePointOutput.model_validate_json(serialized) assert deserialized == sp_output assert deserialized.results == sp_output.results assert deserialized.input_data == sp_output.input_data