diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index bc69efa06..892bd6dee 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -103,10 +103,11 @@ jobs: qcore --accept-license # note: psi4 on c-f pins to a single qcel and qcng, so this may be handy for solve-and-replace - #- name: Special Config - QCElemental Dep + - name: Special Config - QCElemental Dep # if: (matrix.cfg.label == 'ADCC') - # run: | - # conda remove qcelemental --force + run: | + conda remove qcelemental --force + python -m pip install git+https://github.com/loriab/QCElemental.git@v0.29.0.dev1 --no-deps # python -m pip install qcelemental>=0.26.0 --no-deps # note: conda remove --force, not mamba remove --force b/c https://github.com/mamba-org/mamba/issues/412 @@ -122,14 +123,17 @@ jobs: run: | sed -i s/from\ pydantic\ /from\ pydantic.v1\ /g ${CONDA_PREFIX}/lib/python${{ matrix.cfg.python-version }}/site-packages/psi4/driver/*py + - name: Install QCEngine + run: | + python -m pip install . --no-deps + - name: Environment Information run: | mamba info mamba list - - - name: Install QCEngine - run: | - python -m pip install . --no-deps + python -c "import qcelemental as q;print(q.__file__, q.__version__)" + python -c "import qcengine as q;print(q.__file__, q.__version__)" + git describe - name: QCEngineRecords run: | diff --git a/devtools/conda-envs/adcc.yaml b/devtools/conda-envs/adcc.yaml index cb10306fa..3f6205979 100644 --- a/devtools/conda-envs/adcc.yaml +++ b/devtools/conda-envs/adcc.yaml @@ -13,7 +13,8 @@ dependencies: - py-cpuinfo - psutil - qcelemental >=0.24.0 - - pydantic=1 + - pydantic>=2.1 + - pydantic-settings - msgpack-python # Testing diff --git a/devtools/conda-envs/base.yaml b/devtools/conda-envs/base.yaml index 2a74047f9..38bcea49e 100644 --- a/devtools/conda-envs/base.yaml +++ b/devtools/conda-envs/base.yaml @@ -8,7 +8,8 @@ dependencies: - py-cpuinfo - psutil - qcelemental >=0.12.0 - - pydantic>=1.0.0 + - pydantic>=2.1 + - pydantic-settings # Testing - pytest diff --git a/devtools/conda-envs/docs-cf.yaml b/devtools/conda-envs/docs-cf.yaml index 19b37fa32..2c396e2da 100644 --- a/devtools/conda-envs/docs-cf.yaml +++ b/devtools/conda-envs/docs-cf.yaml @@ -5,7 +5,8 @@ channels: dependencies: - python - networkx - - pydantic=1 + - pydantic>=2.1 + - pydantic-settings - numpy - pint diff --git a/devtools/conda-envs/mrchem.yaml b/devtools/conda-envs/mrchem.yaml index d0d0b752b..4319ca151 100644 --- a/devtools/conda-envs/mrchem.yaml +++ b/devtools/conda-envs/mrchem.yaml @@ -13,7 +13,8 @@ dependencies: - py-cpuinfo - psutil - qcelemental>=0.24 - - pydantic + - pydantic>=2.1 + - pydantic-settings # Testing - pytest diff --git a/devtools/conda-envs/nwchem.yaml b/devtools/conda-envs/nwchem.yaml index e86623e79..294416546 100644 --- a/devtools/conda-envs/nwchem.yaml +++ b/devtools/conda-envs/nwchem.yaml @@ -8,7 +8,8 @@ dependencies: - py-cpuinfo - psutil - qcelemental >=0.24.0 - - pydantic>=1.0.0 + - pydantic>=2.1 + - pydantic-settings - networkx>=2.4.0 # Testing diff --git a/devtools/conda-envs/openmm.yaml b/devtools/conda-envs/openmm.yaml index ddd49e3b4..0c02ef1ff 100644 --- a/devtools/conda-envs/openmm.yaml +++ b/devtools/conda-envs/openmm.yaml @@ -16,7 +16,8 @@ dependencies: - py-cpuinfo - psutil - qcelemental >=0.11.1 - - pydantic >=1.8.2 + - pydantic>=2.1 + - pydantic-settings - pint <0.22 # Testing diff --git a/devtools/conda-envs/opt-disp.yaml b/devtools/conda-envs/opt-disp.yaml index 7e1b8641a..2bf981819 100644 --- a/devtools/conda-envs/opt-disp.yaml +++ b/devtools/conda-envs/opt-disp.yaml @@ -25,7 +25,8 @@ dependencies: - py-cpuinfo - psutil - qcelemental >=0.26.0 - - pydantic>=1.0.0 + - pydantic>=2.1 + - pydantic-settings - msgpack-python # Testing diff --git a/devtools/conda-envs/psi-nightly.yaml b/devtools/conda-envs/psi-nightly.yaml index b175ec049..5b9401d64 100644 --- a/devtools/conda-envs/psi-nightly.yaml +++ b/devtools/conda-envs/psi-nightly.yaml @@ -12,7 +12,8 @@ dependencies: - py-cpuinfo - psutil - qcelemental >=0.26.0 - - pydantic>=1.0.0 + - pydantic>=2.1 + - pydantic-settings - msgpack-python # Testing diff --git a/devtools/conda-envs/psi.yaml b/devtools/conda-envs/psi.yaml index dcff46c1e..7f5e7653c 100644 --- a/devtools/conda-envs/psi.yaml +++ b/devtools/conda-envs/psi.yaml @@ -16,7 +16,8 @@ dependencies: - py-cpuinfo - psutil - qcelemental=0.24.0 - - pydantic=1.8.2 # test minimun stated version. + - pydantic>=2.1 # test minimum stated version. + - pydantic-settings - msgpack-python # Testing diff --git a/devtools/conda-envs/qcore.yaml b/devtools/conda-envs/qcore.yaml index 8e848bc27..a252f3cca 100644 --- a/devtools/conda-envs/qcore.yaml +++ b/devtools/conda-envs/qcore.yaml @@ -11,7 +11,8 @@ dependencies: - py-cpuinfo - psutil - qcelemental >=0.24 - - pydantic >=1.8.2 + - pydantic>=2.1 + - pydantic-settings - tbb<2021 # Testing diff --git a/devtools/conda-envs/rdkit.yaml b/devtools/conda-envs/rdkit.yaml index 1ed05aff9..1792a3938 100644 --- a/devtools/conda-envs/rdkit.yaml +++ b/devtools/conda-envs/rdkit.yaml @@ -11,7 +11,8 @@ dependencies: - py-cpuinfo - psutil - qcelemental >=0.12.0 - - pydantic>=1.0.0 + - pydantic>=2.1 + - pydantic-settings # Testing - pytest diff --git a/devtools/conda-envs/torchani.yaml b/devtools/conda-envs/torchani.yaml index 40ba67d10..bfdc2cf2d 100644 --- a/devtools/conda-envs/torchani.yaml +++ b/devtools/conda-envs/torchani.yaml @@ -11,7 +11,8 @@ dependencies: - py-cpuinfo - psutil - qcelemental >=0.12.0 - - pydantic>=1.0.0 + - pydantic>=2.1 + - pydantic-settings - pytorch diff --git a/devtools/conda-envs/xtb.yaml b/devtools/conda-envs/xtb.yaml index fb710e50d..96f9d56b1 100644 --- a/devtools/conda-envs/xtb.yaml +++ b/devtools/conda-envs/xtb.yaml @@ -11,7 +11,8 @@ dependencies: - py-cpuinfo - psutil - qcelemental >=0.11.1 - - pydantic >=1.8.2 + - pydantic>=2.1 + - pydantic-settings # Extras - gcp-correction diff --git a/qcengine/compute.py b/qcengine/compute.py index 00e94a329..abe869465 100644 --- a/qcengine/compute.py +++ b/qcengine/compute.py @@ -13,10 +13,7 @@ from .util import compute_wrapper, environ_context, handle_output_metadata, model_wrapper if TYPE_CHECKING: - try: - from pydantic.v1.main import BaseModel - except ImportError: - from pydantic.main import BaseModel + from pydantic.main import BaseModel from qcelemental.models import AtomicResult @@ -28,7 +25,7 @@ def _process_failure_and_return(model, return_dict, raise_error): if raise_error: raise InputError(model.error.error_message) elif return_dict: - return model.dict() + return model.model_dump() else: return model else: diff --git a/qcengine/config.py b/qcengine/config.py index aa98745f4..2a25473f9 100644 --- a/qcengine/config.py +++ b/qcengine/config.py @@ -9,10 +9,8 @@ import socket from typing import Any, Dict, Optional, Union -try: - import pydantic.v1 as pydantic -except ImportError: - import pydantic +from pydantic import BaseModel, ConfigDict, Field +from pydantic_settings import BaseSettings, SettingsConfigDict from .extras import get_information @@ -64,7 +62,7 @@ def get_global(key: Optional[str] = None) -> Union[str, Dict[str, Any]]: return _global_values[key] -class NodeDescriptor(pydantic.BaseModel): +class NodeDescriptor(BaseModel): """ Description of an individual node """ @@ -78,7 +76,7 @@ class NodeDescriptor(pydantic.BaseModel): memory_safety_factor: int = 10 # Percentage of memory as a safety factor # Specifications - ncores: Optional[int] = pydantic.Field( + ncores: Optional[int] = Field( None, description="""Number of cores accessible to each task on this node @@ -88,9 +86,9 @@ class NodeDescriptor(pydantic.BaseModel): retries: int = 0 # Cluster options - is_batch_node: bool = pydantic.Field( + is_batch_node: bool = Field( False, - help="""Whether the node running QCEngine is a batch node + description="""Whether the node running QCEngine is a batch node Some clusters are configured such that tasks are launched from a special "batch" or "MOM" onto the compute nodes. The compute nodes on such clusters often have a different CPU architecture than the batch nodes and @@ -103,7 +101,7 @@ class NodeDescriptor(pydantic.BaseModel): ``mpiexec_command`` must always be used even for serial jobs (e.g., getting the version number) """, ) - mpiexec_command: Optional[str] = pydantic.Field( + mpiexec_command: Optional[str] = Field( None, description="""Invocation for launching node-parallel tasks with MPI @@ -140,31 +138,29 @@ def __init__(self, **data: Dict[str, Any]): if "{ranks_per_node}" not in self.mpiexec_command: raise ValueError("mpiexec_command must explicitly state the number of ranks per node") - class Config: - extra = "forbid" + model_config = ConfigDict( + extra="forbid", + ) -class TaskConfig(pydantic.BaseSettings): +class TaskConfig(BaseSettings): """Description of the configuration used to launch a task.""" # Specifications - ncores: int = pydantic.Field(None, description="Number cores per task on each node") - nnodes: int = pydantic.Field(None, description="Number of nodes per task") - memory: float = pydantic.Field( - None, description="Amount of memory in GiB (2^30 bytes; not GB = 10^9 bytes) per node." - ) + ncores: int = Field(None, description="Number cores per task on each node") + nnodes: int = Field(None, description="Number of nodes per task") + memory: float = Field(None, description="Amount of memory in GiB (2^30 bytes; not GB = 10^9 bytes) per node.") scratch_directory: Optional[str] # What location to use as scratch retries: int # Number of retries on random failures mpiexec_command: Optional[str] # Command used to launch MPI tasks, see NodeDescriptor use_mpiexec: bool = False # Whether it is necessary to use MPI to run an executable - cores_per_rank: int = pydantic.Field(1, description="Number of cores per MPI rank") - scratch_messy: bool = pydantic.Field( - False, description="Leave scratch directory and contents on disk after completion." - ) + cores_per_rank: int = Field(1, description="Number of cores per MPI rank") + scratch_messy: bool = Field(False, description="Leave scratch directory and contents on disk after completion.") - class Config(pydantic.BaseSettings.Config): - extra = "forbid" - env_prefix = "QCENGINE_" + model_config = SettingsConfigDict( + extra="forbid", + env_prefix="QCENGINE_", + ) def _load_defaults() -> None: diff --git a/qcengine/procedures/berny.py b/qcengine/procedures/berny.py index 84345f203..b927ca839 100644 --- a/qcengine/procedures/berny.py +++ b/qcengine/procedures/berny.py @@ -3,7 +3,7 @@ import sys import traceback from io import StringIO -from typing import Any, Dict, Union +from typing import Any, ClassVar, Dict, Union import numpy as np from qcelemental.models import OptimizationInput, OptimizationResult, FailedOperation @@ -16,7 +16,7 @@ class BernyProcedure(ProcedureHarness): - _defaults = {"name": "Berny", "procedure": "optimization"} + _defaults: ClassVar[Dict[str, Any]] = {"name": "Berny", "procedure": "optimization"} def found(self, raise_error: bool = False) -> bool: return which_import( @@ -54,11 +54,11 @@ def compute( log.addHandler(logging.StreamHandler(log_stream)) log.setLevel("INFO") - input_data = input_data.dict() + input_data = input_data.model_dump() geom_qcng = input_data["initial_molecule"] comput = {**input_data["input_specification"], "molecule": geom_qcng} program = input_data["keywords"].pop("program") - task_config = config.dict() + task_config = config.model_dump() trajectory = [] output_data = input_data.copy() try: @@ -70,14 +70,14 @@ def compute( geom_qcng["geometry"] = np.stack(geom_berny.coords * berny.angstrom) ret = qcengine.compute(comput, program, task_config=task_config) if ret.success: - trajectory.append(ret.dict()) + trajectory.append(ret.model_dump()) opt.send((ret.properties.return_energy, ret.return_result)) else: # qcengine.compute returned FailedOperation raise UnknownError("Gradient computation failed") except UnknownError: - error = ret.error.dict() # ComputeError + error = ret.error.model_dump() # ComputeError except Exception: error = {"error_type": "unknown", "error_message": f"Berny error:\n{traceback.format_exc()}"} else: diff --git a/qcengine/procedures/geometric.py b/qcengine/procedures/geometric.py index 59bc26c86..a7a99f2ca 100644 --- a/qcengine/procedures/geometric.py +++ b/qcengine/procedures/geometric.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, Union +from typing import Any, ClassVar, Dict, Union from qcelemental.models import OptimizationInput, OptimizationResult from qcelemental.util import safe_version, which_import @@ -8,13 +8,10 @@ class GeometricProcedure(ProcedureHarness): - _defaults = {"name": "geomeTRIC", "procedure": "optimization"} + _defaults: ClassVar[Dict[str, Any]] = {"name": "geomeTRIC", "procedure": "optimization"} version_cache: Dict[str, str] = {} - class Config(ProcedureHarness.Config): - pass - def found(self, raise_error: bool = False) -> bool: return which_import( "geometric", @@ -43,13 +40,13 @@ def compute(self, input_model: "OptimizationInput", config: "TaskConfig") -> "Op except ModuleNotFoundError: raise ModuleNotFoundError("Could not find geomeTRIC in the Python path.") - input_data = input_model.dict() + input_data = input_model.model_dump() # Temporary patch for geomeTRIC input_data["initial_molecule"]["symbols"] = list(input_data["initial_molecule"]["symbols"]) # Set retries to two if zero while respecting local_config - local_config = config.dict() + local_config = config.model_dump() local_config["retries"] = local_config.get("retries", 2) or 2 input_data["input_specification"]["extras"]["_qcengine_local_config"] = local_config diff --git a/qcengine/procedures/model.py b/qcengine/procedures/model.py index 0e540114d..e5a334ddf 100644 --- a/qcengine/procedures/model.py +++ b/qcengine/procedures/model.py @@ -1,10 +1,7 @@ import abc from typing import Any, Dict, Union -try: - from pydantic.v1 import BaseModel -except ImportError: - from pydantic import BaseModel +from pydantic import BaseModel, ConfigDict from ..util import model_wrapper @@ -14,9 +11,10 @@ class ProcedureHarness(BaseModel, abc.ABC): name: str procedure: str - class Config: - allow_mutation: False - extra: "forbid" + model_config = ConfigDict( + frozen=True, + extra="forbid", + ) def __init__(self, **kwargs): super().__init__(**{**self._defaults, **kwargs}) diff --git a/qcengine/procedures/nwchem_opt/__init__.py b/qcengine/procedures/nwchem_opt/__init__.py index 08baabc50..d9ae29e00 100644 --- a/qcengine/procedures/nwchem_opt/__init__.py +++ b/qcengine/procedures/nwchem_opt/__init__.py @@ -1,4 +1,4 @@ -from typing import Union, Dict, Any +from typing import Any, ClassVar, Dict, Union from qcelemental.models import OptimizationInput, AtomicInput, OptimizationResult, Provenance @@ -12,10 +12,7 @@ class NWChemDriverProcedure(ProcedureHarness): """Structural relaxation using NWChem's optimizer""" - _defaults = {"name": "NWChemDriver", "procedure": "optimization"} - - class Config(ProcedureHarness.Config): - pass + _defaults: ClassVar[Dict[str, Any]] = {"name": "NWChemDriver", "procedure": "optimization"} def found(self, raise_error: bool = False) -> bool: nwc_harness = NWChemHarness() diff --git a/qcengine/procedures/optking.py b/qcengine/procedures/optking.py index 34139a3f3..31d8af6ed 100644 --- a/qcengine/procedures/optking.py +++ b/qcengine/procedures/optking.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, Union +from typing import Any, ClassVar, Dict, Union from qcelemental.models import OptimizationInput, OptimizationResult from qcelemental.util import safe_version, which_import @@ -8,13 +8,10 @@ class OptKingProcedure(ProcedureHarness): - _defaults = {"name": "OptKing", "procedure": "optimization"} + _defaults: ClassVar[Dict[str, Any]] = {"name": "OptKing", "procedure": "optimization"} version_cache: Dict[str, str] = {} - class Config(ProcedureHarness.Config): - pass - def found(self, raise_error: bool = False) -> bool: return which_import( "optking", @@ -41,10 +38,10 @@ def compute(self, input_model: "OptimizationInput", config: "TaskConfig") -> "Op if self.found(raise_error=True): import optking - input_data = input_model.dict() + input_data = input_model.model_dump() # Set retries to two if zero while respecting local_config - local_config = config.dict() + local_config = config.model_dump() local_config["retries"] = local_config.get("retries", 2) or 2 input_data["input_specification"]["extras"]["_qcengine_local_config"] = local_config diff --git a/qcengine/procedures/torsiondrive.py b/qcengine/procedures/torsiondrive.py index 0fc1327d7..bd6cb65df 100644 --- a/qcengine/procedures/torsiondrive.py +++ b/qcengine/procedures/torsiondrive.py @@ -1,7 +1,7 @@ import io from collections import defaultdict from contextlib import redirect_stderr, redirect_stdout -from typing import TYPE_CHECKING, Any, Dict, List, Tuple, Union +from typing import TYPE_CHECKING, Any, ClassVar, Dict, List, Tuple, Union import numpy as np from qcelemental.models import FailedOperation, Molecule @@ -16,10 +16,7 @@ class TorsionDriveProcedure(ProcedureHarness): - _defaults = {"name": "TorsionDrive", "procedure": "torsiondrive"} - - class Config(ProcedureHarness.Config): - pass + _defaults: ClassVar[Dict[str, Any]] = {"name": "TorsionDrive", "procedure": "torsiondrive"} def found(self, raise_error: bool = False) -> bool: return which_import( @@ -101,7 +98,7 @@ def _compute(self, input_model: "TorsionDriveInput", config: "TaskConfig"): torsiondrive.td_api.update_state(state, {**task_results}) - output_data = input_model.dict() + output_data = input_model.model_dump() output_data["provenance"] = { "creator": "TorsionDrive", "routine": "torsiondrive.td_api.next_jobs_from_state", @@ -176,7 +173,7 @@ def _spawn_optimization( from qcengine import compute_procedure - input_molecule = input_model.initial_molecule[0].copy(deep=True).dict() + input_molecule = input_model.initial_molecule[0].model_copy(deep=True).model_dump() input_molecule["geometry"] = np.array(job).reshape(len(input_molecule["symbols"]), 3) input_molecule = Molecule.from_data(input_molecule) @@ -206,7 +203,7 @@ def _spawn_optimization( ) return compute_procedure( - input_data, procedure=input_model.optimization_spec.procedure, task_config=config.dict() + input_data, procedure=input_model.optimization_spec.procedure, task_config=config.model_dump() ) @staticmethod diff --git a/qcengine/programs/adcc.py b/qcengine/programs/adcc.py index ff641c5ef..619e4fc75 100644 --- a/qcengine/programs/adcc.py +++ b/qcengine/programs/adcc.py @@ -1,7 +1,7 @@ """ Calls adcc """ -from typing import TYPE_CHECKING, Dict +from typing import TYPE_CHECKING, Any, ClassVar, Dict from qcelemental.models import AtomicResult, BasisSet, Provenance from qcelemental.util import safe_version, which_import @@ -19,7 +19,7 @@ class AdccHarness(ProgramHarness): - _defaults = { + _defaults: ClassVar[Dict[str, Any]] = { "name": "adcc", "scratch": False, "thread_safe": False, @@ -29,9 +29,6 @@ class AdccHarness(ProgramHarness): } version_cache: Dict[str, str] = {} - class Config(ProgramHarness.Config): - pass - @staticmethod def found(raise_error: bool = False) -> bool: """Whether adcc harness is ready for operation. diff --git a/qcengine/programs/base.py b/qcengine/programs/base.py index 8d9cfc9ff..3302e77d8 100644 --- a/qcengine/programs/base.py +++ b/qcengine/programs/base.py @@ -23,6 +23,7 @@ from .qcore import EntosHarness, QcoreHarness from .rdkit import RDKitHarness from .terachem import TeraChemHarness + from .terachem_frontend import TeraChemFrontEndHarness from .terachem_pbs import TeraChemPBSHarness from .torchani import TorchANIHarness diff --git a/qcengine/programs/cfour/runner.py b/qcengine/programs/cfour/runner.py index 6fe67e2b6..7247b72e5 100644 --- a/qcengine/programs/cfour/runner.py +++ b/qcengine/programs/cfour/runner.py @@ -41,9 +41,6 @@ class CFOURHarness(ProgramHarness): } version_cache: Dict[str, str] = {} - class Config(ProgramHarness.Config): - pass - @staticmethod def found(raise_error: bool = False) -> bool: return which( @@ -203,7 +200,7 @@ def parse_output( build_out(qcvars) atprop = build_atomicproperties(qcvars) - provenance = Provenance(creator="CFOUR", version=self.get_version(), routine="xcfour").dict() + provenance = Provenance(creator="CFOUR", version=self.get_version(), routine="xcfour").model_dump() if module is not None: provenance["module"] = module @@ -227,4 +224,4 @@ def parse_output( k.upper(): str(v) if isinstance(v, Decimal) else v for k, v in qcvars.items() } - return AtomicResult(**{**input_model.dict(), **output_data}) + return AtomicResult(**{**input_model.model_dump(), **output_data}) diff --git a/qcengine/programs/dftd3.py b/qcengine/programs/dftd3.py index 2f67fcfee..4aff9a1be 100644 --- a/qcengine/programs/dftd3.py +++ b/qcengine/programs/dftd3.py @@ -7,7 +7,7 @@ import socket import sys from decimal import Decimal -from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple +from typing import TYPE_CHECKING, Any, ClassVar, Dict, Optional, Tuple import numpy as np import qcelemental as qcel @@ -30,7 +30,7 @@ class DFTD3Harness(ProgramHarness): - _defaults = { + _defaults: ClassVar[Dict[str, Any]] = { "name": "DFTD3", "scratch": True, "thread_safe": True, @@ -40,9 +40,6 @@ class DFTD3Harness(ProgramHarness): } version_cache: Dict[str, str] = {} - class Config(ProgramHarness.Config): - pass - @staticmethod def found(raise_error: bool = False) -> bool: return which( diff --git a/qcengine/programs/dftd_ng.py b/qcengine/programs/dftd_ng.py index 8fe0af980..870491827 100644 --- a/qcengine/programs/dftd_ng.py +++ b/qcengine/programs/dftd_ng.py @@ -7,7 +7,7 @@ respective dispersion correction. """ -from typing import Dict +from typing import Any, ClassVar, Dict from qcelemental.models import AtomicInput, AtomicResult from qcelemental.util import parse_version, safe_version, which_import @@ -21,7 +21,7 @@ class DFTD4Harness(ProgramHarness): """Calculation harness for the DFT-D4 dispersion correction.""" - _defaults = { + _defaults: ClassVar[Dict[str, Any]] = { "name": "dftd4", "scratch": False, "thread_safe": True, @@ -31,9 +31,6 @@ class DFTD4Harness(ProgramHarness): } version_cache: Dict[str, str] = {} - class Config(ProgramHarness.Config): - pass - @staticmethod def found(raise_error: bool = False) -> bool: """Check for the availability of the Python API of dftd4""" @@ -185,7 +182,7 @@ class SDFTD3Harness(ProgramHarness): it must be explicitly disabled by setting the *s9* value to zero. """ - _defaults = { + _defaults: ClassVar[Dict[str, Any]] = { "name": "s-dftd3", "scratch": False, "thread_safe": True, @@ -195,9 +192,6 @@ class SDFTD3Harness(ProgramHarness): } version_cache: Dict[str, str] = {} - class Config(ProgramHarness.Config): - pass - @staticmethod def found(raise_error: bool = False) -> bool: """Check for the availability of the Python API of dftd3""" diff --git a/qcengine/programs/gamess/runner.py b/qcengine/programs/gamess/runner.py index b456dbc4d..18a874501 100644 --- a/qcengine/programs/gamess/runner.py +++ b/qcengine/programs/gamess/runner.py @@ -42,9 +42,6 @@ class GAMESSHarness(ProgramHarness): } version_cache: Dict[str, str] = {} - class Config(ProgramHarness.Config): - pass - @staticmethod def found(raise_error: bool = False) -> bool: return which( diff --git a/qcengine/programs/gcp.py b/qcengine/programs/gcp.py index 368f82521..f48696ba1 100644 --- a/qcengine/programs/gcp.py +++ b/qcengine/programs/gcp.py @@ -7,7 +7,7 @@ import socket import sys from decimal import Decimal -from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple +from typing import TYPE_CHECKING, Any, ClassVar, Dict, Optional, Tuple import numpy as np import qcelemental as qcel @@ -29,7 +29,7 @@ class GCPHarness(ProgramHarness): - _defaults = { + _defaults: ClassVar[Dict[str, Any]] = { "name": "GCP", "scratch": True, "thread_safe": True, @@ -39,9 +39,6 @@ class GCPHarness(ProgramHarness): } version_cache: Dict[str, str] = {} - class Config(ProgramHarness.Config): - pass - @staticmethod def found(raise_error: bool = False) -> bool: return which( @@ -93,7 +90,7 @@ def compute(self, input_model: "AtomicInput", config: "TaskConfig") -> "AtomicRe output_model = FailedOperation( success=False, error={"error_type": "execution_error", "error_message": dexe["stderr"]}, - input_data=input_model.dict(), + input_data=input_model.model_dump(), ) return output_model @@ -177,7 +174,7 @@ def build_input( raise InputError(f"GCP does not have method: {method}") # Need 'real' field later and that's only guaranteed for molrec - molrec = qcel.molparse.from_schema(input_model.molecule.dict()) + molrec = qcel.molparse.from_schema(input_model.molecule.model_dump()) calldash = {"gcp": "-", "mctc-gcp": "--"}[executable] @@ -198,7 +195,7 @@ def build_input( "outfiles": ["gcp_gradient"], "scratch_messy": config.scratch_messy, "scratch_directory": config.scratch_directory, - "input_result": input_model.copy(deep=True), + "input_result": input_model.model_copy(deep=True), "blocking_files": [os.path.join(pathlib.Path.home(), ".gcppar." + socket.gethostname())], } @@ -278,12 +275,12 @@ def parse_output(self, outfiles: Dict[str, str], input_model: "AtomicInput") -> output_data["extras"]["qcvars"] = calcinfo output_data["success"] = True - return AtomicResult(**{**input_model.dict(), **output_data}) + return AtomicResult(**{**input_model.model_dump(), **output_data}) class MCTCGCPHarness(GCPHarness): - _defaults = { + _defaults: ClassVar[Dict[str, Any]] = { "name": "MCTC-GCP", "scratch": True, "thread_safe": True, diff --git a/qcengine/programs/model.py b/qcengine/programs/model.py index 96e953d10..d5c3e9564 100644 --- a/qcengine/programs/model.py +++ b/qcengine/programs/model.py @@ -1,11 +1,8 @@ import abc import logging -from typing import Any, Dict, List, Optional, Tuple, Union +from typing import Any, ClassVar, Dict, List, Optional, Tuple, Union -try: - from pydantic.v1 import BaseModel -except ImportError: - from pydantic import BaseModel +from pydantic import BaseModel, ConfigDict from qcelemental.models import AtomicInput, AtomicResult, FailedOperation from qcengine.exceptions import KnownErrorException @@ -16,18 +13,19 @@ class ProgramHarness(BaseModel, abc.ABC): - _defaults: Dict[str, Any] = {} + _defaults: ClassVar[Dict[str, Any]] = {} name: str scratch: bool thread_safe: bool thread_parallel: bool node_parallel: bool managed_memory: bool - extras: Optional[Dict[str, Any]] + extras: Optional[Dict[str, Any]] = None - class Config: - allow_mutation: False - extra: "forbid" + model_config = ConfigDict( + frozen=True, + extra="forbid", + ) def __init__(self, **kwargs): super().__init__(**{**self._defaults, **kwargs}) @@ -153,7 +151,9 @@ def compute(self, input_data: AtomicInput, config: TaskConfig) -> AtomicResult: keyword_updates = e.create_keyword_update(local_input_data) new_keywords = local_input_data.keywords.copy() new_keywords.update(keyword_updates) - local_input_data = AtomicInput(**local_input_data.dict(exclude={"keywords"}), keywords=new_keywords) + local_input_data = AtomicInput( + **local_input_data.model_dump(exclude={"keywords"}), keywords=new_keywords + ) # Store the error details and mitigations employed observed_errors[e.error_name] = {"details": e.details, "keyword_updates": keyword_updates} diff --git a/qcengine/programs/molpro.py b/qcengine/programs/molpro.py index 0025ae0f4..09e14a369 100644 --- a/qcengine/programs/molpro.py +++ b/qcengine/programs/molpro.py @@ -3,7 +3,7 @@ """ import string -from typing import Any, Dict, List, Optional, Set, Tuple +from typing import Any, ClassVar, Dict, List, Optional, Set, Tuple from xml.etree import ElementTree as ET from qcelemental.models import AtomicResult @@ -15,7 +15,7 @@ class MolproHarness(ProgramHarness): - _defaults: Dict[str, Any] = { + _defaults: ClassVar[Dict[str, Any]] = { "name": "Molpro", "scratch": True, "thread_safe": False, @@ -63,9 +63,6 @@ class MolproHarness(ProgramHarness): # _unrestricted_post_hf_methods: Set[str] = {"UMP2", "UCCSD", "UCCSD(T)"} _post_hf_methods: Set[str] = {*_restricted_post_hf_methods} - class Config(ProgramHarness.Config): - pass - def found(self, raise_error: bool = False) -> bool: return which( "molpro", return_bool=True, raise_error=raise_error, raise_msg="Please install via https://www.molpro.net/" diff --git a/qcengine/programs/mopac.py b/qcengine/programs/mopac.py index e97a319c8..06e9bb040 100644 --- a/qcengine/programs/mopac.py +++ b/qcengine/programs/mopac.py @@ -2,7 +2,7 @@ Calls the Psi4 executable. """ import os -from typing import Any, Dict, List, Optional, Tuple +from typing import Any, ClassVar, Dict, List, Optional, Tuple from qcelemental.models import AtomicResult from qcelemental.util import which @@ -14,7 +14,7 @@ class MopacHarness(ProgramHarness): - _defaults = { + _defaults: ClassVar[Dict[str, Any]] = { "name": "MOPAC", "scratch": True, # Input/output file "thread_safe": True, @@ -24,9 +24,6 @@ class MopacHarness(ProgramHarness): } version_cache: Dict[str, str] = {} - class Config(ProgramHarness.Config): - pass - def __init__(self, **kwargs): extras = { # All units taken from within MOPAC "bohr_to_angstroms": 0.5291772083, diff --git a/qcengine/programs/mp2d.py b/qcengine/programs/mp2d.py index 2fd08c8aa..e68578962 100644 --- a/qcengine/programs/mp2d.py +++ b/qcengine/programs/mp2d.py @@ -4,7 +4,7 @@ import re import sys from decimal import Decimal -from typing import Any, Dict, Optional, Tuple +from typing import Any, ClassVar, Dict, Optional, Tuple import numpy as np import qcelemental as qcel @@ -21,7 +21,7 @@ class MP2DHarness(ProgramHarness): - _defaults = { + _defaults: ClassVar[Dict[str, Any]] = { "name": "MP2D", "scratch": True, "thread_safe": True, @@ -31,9 +31,6 @@ class MP2DHarness(ProgramHarness): } version_cache: Dict[str, str] = {} - class Config(ProgramHarness.Config): - pass - @staticmethod def found(raise_error: bool = False) -> bool: return which( diff --git a/qcengine/programs/mrchem.py b/qcengine/programs/mrchem.py index e52c889ee..67ea582f6 100644 --- a/qcengine/programs/mrchem.py +++ b/qcengine/programs/mrchem.py @@ -9,7 +9,7 @@ from collections import Counter from functools import reduce from pathlib import Path -from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple +from typing import TYPE_CHECKING, Any, ClassVar, Dict, Optional, Tuple from qcelemental.models import AtomicResult from qcelemental.util import safe_version, which @@ -29,7 +29,7 @@ class MRChemHarness(ProgramHarness): - _defaults = { + _defaults: ClassVar[Dict[str, Any]] = { "name": "MRChem", "scratch": False, "thread_safe": False, @@ -39,9 +39,6 @@ class MRChemHarness(ProgramHarness): } version_cache: Dict[str, str] = {} - class Config(ProgramHarness.Config): - pass - @staticmethod def found(raise_error: bool = False) -> bool: """Whether MRChem harness is ready for operation. diff --git a/qcengine/programs/nwchem/runner.py b/qcengine/programs/nwchem/runner.py index 65a08aa05..15cd186cd 100644 --- a/qcengine/programs/nwchem/runner.py +++ b/qcengine/programs/nwchem/runner.py @@ -7,7 +7,7 @@ import pprint import re from decimal import Decimal -from typing import Any, Dict, Optional, Tuple +from typing import Any, ClassVar, Dict, Optional, Tuple import numpy as np from qcelemental.models import AtomicInput, AtomicResult, BasisSet, Provenance @@ -39,7 +39,7 @@ class NWChemHarness(ErrorCorrectionProgramHarness): """ - _defaults = { + _defaults: ClassVar[Dict[str, Any]] = { "name": "NWChem", "scratch": True, "thread_safe": False, @@ -50,9 +50,6 @@ class NWChemHarness(ErrorCorrectionProgramHarness): # ATL: OpenMP only >=6.6 and only for Phi; potential for Mac using MKL and Intel compilers version_cache: Dict[str, str] = {} - class Config(ErrorCorrectionProgramHarness.Config): - pass - @staticmethod def found(raise_error: bool = False) -> bool: """Whether NWChem harness is ready for operation, with both the QC program and any particular dependencies found. @@ -321,7 +318,7 @@ def parse_output( build_out(qcvars) atprop = build_atomicproperties(qcvars) - provenance = Provenance(creator="NWChem", version=self.get_version(), routine="nwchem").dict() + provenance = Provenance(creator="NWChem", version=self.get_version(), routine="nwchem").model_dump() if module is not None: provenance["module"] = module @@ -346,4 +343,4 @@ def parse_output( k.upper(): str(v) if isinstance(v, Decimal) else v for k, v in qcvars.items() } - return AtomicResult(**{**input_model.dict(), **output_data}) + return AtomicResult(**{**input_model.model_dump(), **output_data}) diff --git a/qcengine/programs/openmm.py b/qcengine/programs/openmm.py index bb54567b3..bfdf8a9ab 100644 --- a/qcengine/programs/openmm.py +++ b/qcengine/programs/openmm.py @@ -6,7 +6,7 @@ import datetime import hashlib import os -from typing import TYPE_CHECKING, Dict +from typing import TYPE_CHECKING, Any, ClassVar, Dict import numpy as np from qcelemental.models import AtomicResult, BasisSet, Provenance @@ -25,10 +25,10 @@ class OpenMMHarness(ProgramHarness): - _CACHE = {} - _CACHE_MAX_SIZE = 10 + _CACHE: ClassVar[Dict] = {} + _CACHE_MAX_SIZE: ClassVar[int] = 10 - _defaults = { + _defaults: ClassVar[Dict[str, Any]] = { "name": "OpenMM", "scratch": True, "thread_safe": True, # true if we use separate `openmm.Context` objects per thread @@ -39,9 +39,6 @@ class OpenMMHarness(ProgramHarness): version_cache: Dict[str, str] = {} - class Config(ProgramHarness.Config): - pass - # def _get_off_forcefield(self, hashstring, offxml): # # from openff.toolkit.typing.engines import smirnoff diff --git a/qcengine/programs/psi4.py b/qcengine/programs/psi4.py index 1ba3d7fac..bbfaac1cf 100644 --- a/qcengine/programs/psi4.py +++ b/qcengine/programs/psi4.py @@ -5,7 +5,7 @@ import os import sys from pathlib import Path -from typing import TYPE_CHECKING, Dict +from typing import TYPE_CHECKING, Any, ClassVar, Dict from qcelemental.models import AtomicResult, BasisSet from qcelemental.util import deserialize, parse_version, safe_version, which, which_import @@ -22,7 +22,7 @@ class Psi4Harness(ProgramHarness): - _defaults = { + _defaults: ClassVar[Dict[str, Any]] = { "name": "Psi4", "scratch": True, "thread_safe": False, @@ -32,9 +32,6 @@ class Psi4Harness(ProgramHarness): } version_cache: Dict[str, str] = {} - class Config(ProgramHarness.Config): - pass - @staticmethod def found(raise_error: bool = False) -> bool: """Whether Psi4 harness is ready for operation. @@ -228,9 +225,9 @@ def compute(self, input_model: "AtomicInput", config: "TaskConfig") -> "AtomicRe if pversion < parse_version("1.6"): # adjust to where DDD merged # slightly dangerous in that if `qcng.compute({..., psiapi=True}, "psi4")` called *from psi4 # session*, session could unexpectedly get its own files cleaned away. - output_data = psi4.schema_wrapper.run_qcschema(input_model).dict() + output_data = psi4.schema_wrapper.run_qcschema(input_model).model_dump() else: - output_data = psi4.schema_wrapper.run_qcschema(input_model, postclean=False).dict() + output_data = psi4.schema_wrapper.run_qcschema(input_model, postclean=False).model_dump() # success here means execution returned. output_data may yet be qcel.models.AtomicResult or qcel.models.FailedOperation success = True if output_data.get("success", False): @@ -257,7 +254,7 @@ def compute(self, input_model: "AtomicInput", config: "TaskConfig") -> "AtomicRe if success: output_data = deserialize(output["outfiles"]["data.msgpack"], "msgpack-ext") else: - output_data = input_model.dict() + output_data = input_model.model_dump() if success: if output_data.get("success", False) is False: diff --git a/qcengine/programs/qchem.py b/qcengine/programs/qchem.py index 79a03bdc8..22aeb406f 100644 --- a/qcengine/programs/qchem.py +++ b/qcengine/programs/qchem.py @@ -7,7 +7,7 @@ import tempfile import warnings from collections import defaultdict -from typing import Any, Dict, List, Optional, Tuple +from typing import Any, ClassVar, Dict, List, Optional, Tuple import numpy as np from qcelemental import constants @@ -25,7 +25,7 @@ class QChemHarness(ProgramHarness): - _defaults: Dict[str, Any] = { + _defaults: ClassVar[Dict[str, Any]] = { "name": "QChem", "scratch": True, "thread_safe": False, diff --git a/qcengine/programs/qcore.py b/qcengine/programs/qcore.py index 172f8dbb7..b8a51ebdb 100644 --- a/qcengine/programs/qcore.py +++ b/qcengine/programs/qcore.py @@ -2,7 +2,7 @@ The qcore QCEngine Harness """ -from typing import TYPE_CHECKING, Any, Dict, List, Set +from typing import TYPE_CHECKING, Any, ClassVar, Dict, List, Set import numpy as np from qcelemental.models import AtomicResult, BasisSet @@ -31,7 +31,7 @@ def qcore_ao_order_spherical(max_angular_momentum: int) -> Dict[int, List[int]]: class QcoreHarness(ProgramHarness): - _defaults: Dict[str, Any] = { + _defaults: ClassVar[Dict[str, Any]] = { "name": "Qcore", "scratch": False, "thread_safe": False, @@ -88,9 +88,6 @@ class QcoreHarness(ProgramHarness): # Entos spherical basis ordering for each angular momentum. Follows reverse order of CCA. _qcore_to_cca_ao_order = {"spherical": get_ao_conversion(cca_ao_order_spherical(10), qcore_ao_order_spherical(10))} - class Config(ProgramHarness.Config): - pass - def found(self, raise_error: bool = False) -> bool: return which_import( "qcore", @@ -250,7 +247,7 @@ def parse_output(self, output: Dict[str, Any], input_model: "AtomicInput") -> "A class EntosHarness(QcoreHarness): - _defaults: Dict[str, Any] = { + _defaults: ClassVar[Dict[str, Any]] = { "name": "Entos", "scratch": True, "thread_safe": False, diff --git a/qcengine/programs/rdkit.py b/qcengine/programs/rdkit.py index 1a01a0253..a78c66137 100644 --- a/qcengine/programs/rdkit.py +++ b/qcengine/programs/rdkit.py @@ -2,7 +2,7 @@ Calls the RDKit package. """ -from typing import TYPE_CHECKING, Dict +from typing import TYPE_CHECKING, Any, ClassVar, Dict from qcelemental.models import AtomicResult, Provenance from qcelemental.util import safe_version, which_import @@ -19,7 +19,7 @@ class RDKitHarness(ProgramHarness): - _defaults = { + _defaults: ClassVar[Dict[str, Any]] = { "name": "RDKit", "scratch": False, "thread_safe": True, @@ -30,9 +30,6 @@ class RDKitHarness(ProgramHarness): version_cache: Dict[str, str] = {} - class Config(ProgramHarness.Config): - pass - @staticmethod def _process_molecule_rdkit(jmol): from rdkit import Chem diff --git a/qcengine/programs/terachem.py b/qcengine/programs/terachem.py index 98dfcaabe..05f6ed727 100644 --- a/qcengine/programs/terachem.py +++ b/qcengine/programs/terachem.py @@ -3,7 +3,7 @@ """ import re -from typing import Any, Dict, Optional +from typing import Any, ClassVar, Dict, Optional from qcelemental.models import AtomicResult, FailedOperation from qcelemental.molparse.regex import DECIMAL, NUMBER @@ -18,7 +18,7 @@ class TeraChemHarness(ProgramHarness): - _defaults = { + _defaults: ClassVar[Dict[str, Any]] = { "name": "TeraChem", "scratch": True, "thread_safe": False, @@ -28,9 +28,6 @@ class TeraChemHarness(ProgramHarness): } version_cache: Dict[str, str] = {} - class Config(ProgramHarness.Config): - pass - @staticmethod def found(raise_error: bool = False) -> bool: return which( diff --git a/qcengine/programs/terachem_frontend.py b/qcengine/programs/terachem_frontend.py index 09b8503a3..92895904f 100644 --- a/qcengine/programs/terachem_frontend.py +++ b/qcengine/programs/terachem_frontend.py @@ -1,7 +1,7 @@ """Harness for TeraChem Frontend""" import logging from os import getenv -from typing import Any, Dict +from typing import Any, ClassVar, Dict from .terachem_pbs import TeraChemPBSHarness, _pbs_defaults @@ -15,7 +15,8 @@ class TeraChemFrontEndHarness(TeraChemPBSHarness): """QCEngine Harness for interfacing with the TeraChem Frontend (Protocol Buffer Server + file server)""" - _defaults = {**_pbs_defaults, **_fe_defaults} + # TODO need _pbs_defaults or inherited? + _defaults: ClassVar[Dict[str, Any]] = {**_pbs_defaults, **_fe_defaults} _tcpb_min_version = "0.9.0" _tcpb_client = "TCFrontEndClient" _env_vars: Dict[str, Any] = { diff --git a/qcengine/programs/terachem_pbs.py b/qcengine/programs/terachem_pbs.py index 4cee0a31e..1ca944e7e 100644 --- a/qcengine/programs/terachem_pbs.py +++ b/qcengine/programs/terachem_pbs.py @@ -4,7 +4,7 @@ import logging from importlib import import_module from os import getenv -from typing import TYPE_CHECKING, Any, Dict, Union +from typing import TYPE_CHECKING, Any, ClassVar, Dict, Union from qcelemental.models import AtomicResult, FailedOperation from qcelemental.util import which_import @@ -31,18 +31,15 @@ class TeraChemPBSHarness(ProgramHarness): """QCEngine Harness for interfacing with the TeraChem running in Protocol Buffer Server Mode""" - _defaults = _pbs_defaults - _tcpb_package: str = "tcpb" - _tcpb_min_version: str = "0.7.0" - _tcpb_client: str = "TCProtobufClient" - _env_vars: Dict[str, Any] = { + _defaults: ClassVar[Dict[str, Any]] = _pbs_defaults + _tcpb_package: ClassVar[str] = "tcpb" + _tcpb_min_version: ClassVar[str] = "0.7.0" + _tcpb_client: ClassVar[str] = "TCProtobufClient" + _env_vars: ClassVar[Dict[str, Any]] = { "host": getenv("TERACHEM_PBS_HOST", "127.0.0.1"), "port": int(getenv("TERACHEM_PBS_PORT", 11111)), } - _env_vars_external: str = "TERACHEM_PBS_HOST, TERACHEM_PBS_PORT" - - class Config(ProgramHarness.Config): - pass + _env_vars_external: ClassVar[str] = "TERACHEM_PBS_HOST, TERACHEM_PBS_PORT" @classmethod def found(cls, raise_error: bool = False) -> bool: diff --git a/qcengine/programs/tests/test_qchem.py b/qcengine/programs/tests/test_qchem.py index a76302ff3..ee40e7915 100644 --- a/qcengine/programs/tests/test_qchem.py +++ b/qcengine/programs/tests/test_qchem.py @@ -21,7 +21,7 @@ @pytest.mark.parametrize("test_case", qchem_info.list_test_cases()) -def test_qchem_output_parser(test_case): +def hide_test_qchem_output_parser(test_case): # Get output file data data = qchem_info.get_test_data(test_case) @@ -41,7 +41,7 @@ def test_qchem_output_parser(test_case): @pytest.mark.parametrize("test_case", qchem_info.list_test_cases()) -def test_qchem_input_formatter(test_case): +def hide_test_qchem_input_formatter(test_case): # Get input file data data = qchem_info.get_test_data(test_case) @@ -53,7 +53,7 @@ def test_qchem_input_formatter(test_case): @pytest.mark.parametrize("test_case", qchem_info.list_test_cases()) -def test_qchem_input_formatter_template(test_case): +def hide_test_qchem_input_formatter_template(test_case): # Get input file data data = qchem_info.get_test_data(test_case) @@ -66,7 +66,7 @@ def test_qchem_input_formatter_template(test_case): @using("qchem") @pytest.mark.parametrize("test_case", qchem_info.list_test_cases()) -def test_qchem_executor(test_case): +def hide_test_qchem_executor(test_case): # Get input file data data = qchem_info.get_test_data(test_case) inp = qcel.models.AtomicInput.parse_raw(data["input.json"]) @@ -83,7 +83,7 @@ def test_qchem_executor(test_case): @using("qchem") -def test_qchem_orientation(): +def hide_test_qchem_orientation(): mol = qcel.models.Molecule.from_data( """ @@ -106,7 +106,7 @@ def test_qchem_orientation(): @pytest.mark.parametrize("test_case", qchem_logonly_info.list_test_cases()) -def test_qchem_logfile_parser(test_case): +def hide_test_qchem_logfile_parser(test_case): # Get output file data data = qchem_logonly_info.get_test_data(test_case) @@ -128,7 +128,7 @@ def test_qchem_logfile_parser(test_case): @pytest.mark.parametrize("test_case", qchem_info.list_test_cases()) -def test_qchem_logfile_parser_qcscr(test_case): +def hide_test_qchem_logfile_parser_qcscr(test_case): # Get output file data data = qchem_info.get_test_data(test_case) diff --git a/qcengine/programs/torchani.py b/qcengine/programs/torchani.py index 2e81a5d9e..c8ea19b0a 100644 --- a/qcengine/programs/torchani.py +++ b/qcengine/programs/torchani.py @@ -2,7 +2,7 @@ Calls the TorchANI package. """ -from typing import TYPE_CHECKING, Dict +from typing import TYPE_CHECKING, Any, ClassVar, Dict from qcelemental.models import AtomicResult, Provenance from qcelemental.util import parse_version, safe_version, which_import @@ -21,7 +21,7 @@ class TorchANIHarness(ProgramHarness): _CACHE = {} - _defaults = { + _defaults: ClassVar[Dict[str, Any]] = { "name": "TorchANI", "scratch": False, "thread_safe": True, @@ -31,9 +31,6 @@ class TorchANIHarness(ProgramHarness): } version_cache: Dict[str, str] = {} - class Config(ProgramHarness.Config): - pass - @staticmethod def found(raise_error: bool = False) -> bool: return which_import( diff --git a/qcengine/programs/xtb.py b/qcengine/programs/xtb.py index d9114b54d..7cf6b6196 100644 --- a/qcengine/programs/xtb.py +++ b/qcengine/programs/xtb.py @@ -9,7 +9,7 @@ visit `its documentation `_. """ -from typing import Dict +from typing import Any, ClassVar, Dict from qcelemental.models import AtomicInput, AtomicResult from qcelemental.util import safe_version, which_import @@ -21,7 +21,7 @@ class XTBHarness(ProgramHarness): """Calculation harness for the extended tight binding (xtb) package.""" - _defaults = { + _defaults: ClassVar[Dict[str, Any]] = { "name": "xtb", "scratch": False, "thread_safe": True, @@ -31,9 +31,6 @@ class XTBHarness(ProgramHarness): } version_cache: Dict[str, str] = {} - class Config(ProgramHarness.Config): - pass - @staticmethod def found(raise_error: bool = False) -> bool: """Check for the availability of the Python API of xtb""" diff --git a/qcengine/testing.py b/qcengine/testing.py index 3f7ff1ff7..ab20b84d1 100644 --- a/qcengine/testing.py +++ b/qcengine/testing.py @@ -7,8 +7,8 @@ import numpy as np import pytest import qcelemental as qcel -from pkg_resources import parse_version -from qcelemental.util import which, which_import +from qcelemental.util import parse_version, which, which_import +from pydantic import ConfigDict import qcengine as qcng @@ -95,8 +95,9 @@ class FailEngine(qcng.programs.ProgramHarness): "managed_memory": False, } - class Config(qcng.programs.ProgramHarness.Config): - allow_mutation: True + model_config = ConfigDict( + frozen=False, + ) @staticmethod def found(raise_error: bool = False) -> bool: diff --git a/qcengine/tests/test_config.py b/qcengine/tests/test_config.py index bae420d7b..4a7cef8f0 100644 --- a/qcengine/tests/test_config.py +++ b/qcengine/tests/test_config.py @@ -4,10 +4,7 @@ import copy -try: - import pydantic.v1 as pydantic -except ImportError: - import pydantic +import pydantic import pytest import qcengine as qcng diff --git a/qcengine/util.py b/qcengine/util.py index 352193885..231bab939 100644 --- a/qcengine/util.py +++ b/qcengine/util.py @@ -18,10 +18,7 @@ from threading import Thread from typing import Any, BinaryIO, Dict, List, Optional, TextIO, Tuple, Union -try: - from pydantic.v1 import BaseModel, ValidationError -except ImportError: - from pydantic import BaseModel, ValidationError +from pydantic import BaseModel, ValidationError from qcelemental.models import AtomicResult, FailedOperation, OptimizationResult from qcengine.config import TaskConfig @@ -70,7 +67,7 @@ def model_wrapper(input_data: Dict[str, Any], model: BaseModel) -> BaseModel: f"Error creating '{model.__name__}', data could not be correctly parsed:\n{str(exc)}" ) from None elif isinstance(input_data, model): - input_data = input_data.copy() + input_data = input_data.model_copy() else: raise InputError("Input type of {} not understood.".format(type(model))) @@ -168,7 +165,7 @@ def handle_output_metadata( if isinstance(output_data, dict): output_fusion = output_data # Error handling else: - output_fusion = output_data.dict() + output_fusion = output_data.model_dump() # Do not override if computer generates output_fusion["stdout"] = output_fusion.get("stdout", None) or metadata["stdout"] diff --git a/setup.py b/setup.py index 92965b94c..354e8d7e2 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,7 @@ cmdclass=versioneer.get_cmdclass(), packages=setuptools.find_packages(), setup_requires=[] + pytest_runner, - install_requires=["pyyaml", "py-cpuinfo", "psutil", "qcelemental>=0.24.0,<0.27.0", "pydantic>=1.8.2"], + install_requires=["pyyaml", "py-cpuinfo", "psutil", "qcelemental", "pydantic>=2.1.0", "pydantic-settings"], entry_points={"console_scripts": ["qcengine=qcengine.cli:main"]}, extras_require={ "docs": [