diff --git a/devtools/conda-envs/minimal.yaml b/devtools/conda-envs/minimal.yaml index bf609d50d..0d1b8f070 100644 --- a/devtools/conda-envs/minimal.yaml +++ b/devtools/conda-envs/minimal.yaml @@ -8,7 +8,7 @@ dependencies: - nomkl - python - pint=0.10.0 # technically, qcel has no lower bound for pint version for py36,37 but needs 0.10 for 38 - - pydantic=1.2.0 # technically, qcel works with 1.0.0 but c-f doesn't have py38 builds for it + - pydantic=1.5.0 # Testing - pytest=4.6.4 # technically, qcel works with 4.0.0 but c-f doesn't have py38 builds for it diff --git a/qcelemental/models/__init__.py b/qcelemental/models/__init__.py index f2c6102ce..fe1420d98 100644 --- a/qcelemental/models/__init__.py +++ b/qcelemental/models/__init__.py @@ -14,3 +14,13 @@ from .molecule import Molecule from .procedures import Optimization, OptimizationInput, OptimizationResult from .results import AtomicInput, AtomicResult, AtomicResultProperties, Result, ResultInput, ResultProperties + + +def qcschema_models(): + return [ + AtomicInput, + AtomicResult, + BasisSet, + Molecule, + Provenance, + ] diff --git a/qcelemental/models/basis.py b/qcelemental/models/basis.py index 952bd9406..7621cf6a8 100644 --- a/qcelemental/models/basis.py +++ b/qcelemental/models/basis.py @@ -130,7 +130,7 @@ class BasisSet(ProtoModel): A quantum chemistry basis description. """ - schema_name: constr(strip_whitespace=True, regex="qcschema_basis") = "qcschema_basis" + schema_name: constr(strip_whitespace=True, regex="qcschema_basis") = "qcschema_basis" # type: ignore schema_version: int = 1 name: str = Field(..., description="A standard basis name if available (e.g., 'cc-pVDZ').") diff --git a/qcelemental/models/molecule.py b/qcelemental/models/molecule.py index a7ba80ba7..a22c78f4e 100644 --- a/qcelemental/models/molecule.py +++ b/qcelemental/models/molecule.py @@ -5,13 +5,19 @@ import hashlib import json import warnings +from functools import partial from pathlib import Path -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union +from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Optional, Tuple, Union, cast import numpy as np from pydantic import Field, constr, validator -from ..molparse import from_arrays, from_schema, from_string, to_schema, to_string +# molparse imports separated b/c https://github.com/python/mypy/issues/7203 +from ..molparse.from_arrays import from_arrays +from ..molparse.from_schema import from_schema +from ..molparse.from_string import from_string +from ..molparse.to_schema import to_schema +from ..molparse.to_string import to_string from ..periodic_table import periodictable from ..physical_constants import constants from ..testing import compare, compare_values @@ -23,7 +29,6 @@ if TYPE_CHECKING: from pydantic.typing import ReprArgs - # Rounding quantities for hashing GEOMETRY_NOISE = 8 MASS_NOISE = 6 @@ -225,8 +230,8 @@ class Molecule(ProtoModel): None, description="Maximal point group symmetry which ``geometry`` should be treated. Lowercase." ) # Extra - provenance: Provenance = Field( # type: ignore - provenance_stamp(__name__), + provenance: Provenance = Field( + default_factory=partial(provenance_stamp, __name__), description="The provenance information about how this Molecule (and its attributes) were generated, " "provided, and manipulated.", ) @@ -1042,7 +1047,7 @@ def nuclear_repulsion_energy(self, ifr: int = None) -> float: Nuclear repulsion energy in entire molecule or in fragment. """ - Zeff = [z * int(real) for z, real in zip(self.atomic_numbers, self.real)] + Zeff = [z * int(real) for z, real in zip(cast(Iterable[int], self.atomic_numbers), self.real)] atoms = list(range(self.geometry.shape[0])) if ifr is not None: @@ -1068,7 +1073,7 @@ def nelectrons(self, ifr: int = None) -> int: Number of electrons in entire molecule or in fragment. """ - Zeff = [z * int(real) for z, real in zip(self.atomic_numbers, self.real)] + Zeff = [z * int(real) for z, real in zip(cast(Iterable[int], self.atomic_numbers), self.real)] if ifr is None: nel = sum(Zeff) - self.molecular_charge @@ -1146,7 +1151,7 @@ def align( runiq = np.asarray( [ hashlib.sha1((sym + str(mas)).encode("utf-8")).hexdigest() - for sym, mas in zip(ref_mol.symbols, ref_mol.masses) + for sym, mas in zip(cast(Iterable[str], ref_mol.symbols), ref_mol.masses) ] ) concern_mol = self @@ -1157,7 +1162,7 @@ def align( cuniq = np.asarray( [ hashlib.sha1((sym + str(mas)).encode("utf-8")).hexdigest() - for sym, mas in zip(concern_mol.symbols, concern_mol.masses) + for sym, mas in zip(cast(Iterable[str], concern_mol.symbols), concern_mol.masses) ] ) @@ -1293,7 +1298,7 @@ def scramble( runiq = np.asarray( [ hashlib.sha1((sym + str(mas)).encode("utf-8")).hexdigest() - for sym, mas in zip(ref_mol.symbols, ref_mol.masses) + for sym, mas in zip(cast(Iterable[str], ref_mol.symbols), ref_mol.masses) ] ) nat = rgeom.shape[0] diff --git a/qcelemental/models/results.py b/qcelemental/models/results.py index 92bf46523..ca4398954 100644 --- a/qcelemental/models/results.py +++ b/qcelemental/models/results.py @@ -1,4 +1,5 @@ from enum import Enum +from functools import partial from typing import TYPE_CHECKING, Any, Dict, Optional, Set, Union import numpy as np @@ -347,7 +348,9 @@ class AtomicInput(ProtoModel): extras: Dict[str, Any] = Field({}, description="Extra fields that are not part of the schema.") - provenance: Provenance = Field(Provenance(**provenance_stamp(__name__)), description=str(Provenance.__base_doc__)) + provenance: Provenance = Field( + default_factory=partial(provenance_stamp, __name__), description=str(Provenance.__base_doc__) + ) def __repr_args__(self) -> "ReprArgs": return [ diff --git a/qcelemental/molparse/chgmult.py b/qcelemental/molparse/chgmult.py index 32d53d662..7d1ff4703 100644 --- a/qcelemental/molparse/chgmult.py +++ b/qcelemental/molparse/chgmult.py @@ -360,8 +360,8 @@ def validate_and_fill_chgmult( cgmp_rules.append("4") for ifr in range(nfr): cgmp_range.append( - lambda c, fc, m, fm, ifr=ifr: _sufficient_electrons_for_mult(fzel[ifr], fc[ifr], fm[ifr]) - ) # type: ignore + lambda c, fc, m, fm, ifr=ifr: _sufficient_electrons_for_mult(fzel[ifr], fc[ifr], fm[ifr]) # type: ignore + ) cgmp_rules.append("4-" + str(ifr)) # * (R5) require total parity consistent among neutral_electrons, chg, and mult diff --git a/qcelemental/molparse/to_string.py b/qcelemental/molparse/to_string.py index a83c83642..ff0b6d8d2 100644 --- a/qcelemental/molparse/to_string.py +++ b/qcelemental/molparse/to_string.py @@ -384,7 +384,7 @@ def to_dict(self) -> Dict: atom_format = "{elem}" ghost_format = "@{elem}" - umap = {"bohr": True, "angstrom": False} + umap = {"bohr": "True", "angstrom": "False"} atoms = _atoms_formatter(molrec, geom, atom_format, ghost_format, width, prec, 2) diff --git a/qcelemental/molutil/molecular_formula.py b/qcelemental/molutil/molecular_formula.py index 3fee6ca40..7ff1d8fdb 100644 --- a/qcelemental/molutil/molecular_formula.py +++ b/qcelemental/molutil/molecular_formula.py @@ -1,6 +1,6 @@ import collections import re -from typing import List +from typing import Dict, List def order_molecular_formula(formula: str, order: str = "alphabetical") -> str: @@ -23,7 +23,7 @@ def order_molecular_formula(formula: str, order: str = "alphabetical") -> str: matches = re.findall(r"[A-Z][^A-Z]*", formula) if not "".join(matches) == formula: raise ValueError(f"{formula} is not a valid molecular formula.") - count = collections.defaultdict(int) + count: Dict[str, int] = collections.defaultdict(int) for match in matches: match_n = re.match(r"(\D+)(\d*)", match) assert match_n diff --git a/qcelemental/tests/test_molecule.py b/qcelemental/tests/test_molecule.py index 41e833602..aa56b0d79 100644 --- a/qcelemental/tests/test_molecule.py +++ b/qcelemental/tests/test_molecule.py @@ -651,14 +651,14 @@ def test_show(): def test_molecule_connectivity(): data = {"geometry": np.random.rand(5, 3), "symbols": ["he"] * 5, "validate": False} - mol = Molecule(**data, connectivity=None) + Molecule(**data, connectivity=None) connectivity = [[n, n + 1, 1] for n in range(4)] - mol = Molecule(**data, connectivity=connectivity) + Molecule(**data, connectivity=connectivity) connectivity[0][0] = -1 with pytest.raises(ValueError): - mol = Molecule(**data, connectivity=connectivity) + Molecule(**data, connectivity=connectivity) def test_orient_nomasses(): @@ -719,7 +719,7 @@ def test_sparse_molecule_connectivity(): def test_bad_isotope_spec(): - with pytest.raises(NotAnElementError) as e: + with pytest.raises(NotAnElementError): qcel.models.Molecule(symbols=["He3"], geometry=[0, 0, 0]) diff --git a/qcelemental/util/importing.py b/qcelemental/util/importing.py index 4bd120989..cacd3b81b 100644 --- a/qcelemental/util/importing.py +++ b/qcelemental/util/importing.py @@ -1,7 +1,7 @@ import os import shutil import sys -from typing import Union +from typing import List, Union def which_import( @@ -12,7 +12,7 @@ def which_import( raise_msg: str = None, package: str = None, namespace_ok: bool = False, -) -> Union[bool, None, str]: +) -> Union[bool, None, str, List[str]]: """Tests to see if a Python module is available. Returns diff --git a/setup.py b/setup.py index b88ce62aa..345fc00e9 100644 --- a/setup.py +++ b/setup.py @@ -31,7 +31,7 @@ package_data={'': [os.path.join('qcelemental', 'data', '*.json')]}, setup_requires=[] + pytest_runner, python_requires='>=3.6', - install_requires=['numpy >= 1.12.0', 'pint >= 0.10.0', 'pydantic >= 1.0.0'], + install_requires=["numpy >= 1.12.0", "pint >= 0.10.0", "pydantic >= 1.5.0"], extras_require={ 'docs': [ 'numpydoc',