Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 0 additions & 25 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@ jobs:
exclude:
- rdkit: false
openeye: false
- openeye: true
python-version: "3.11"
- openeye: true
python-version: "3.12"
- openeye: true
Expand Down Expand Up @@ -138,12 +136,6 @@ jobs:
run: pytest -r fE --tb=short openff/toolkit/_tests/test_links.py

- name: Run mypy
# When possible re-enable this with OE+RDK=True, but for now rdkit builds often have bugs
# in stubs and there are other subtleties (in the source code and builds) that reduce the number
# of available builds.
# See ex https://github.com/rdkit/rdkit/issues/7221
# and https://github.com/rdkit/rdkit/issues/7583
if: ${{ matrix.rdkit == false && matrix.openeye == true }}
run: mypy -p "openff.toolkit"

- name: Run unit tests
Expand All @@ -156,23 +148,6 @@ jobs:

python -m pytest -x --durations=20 $PYTEST_ARGS $COV openff/toolkit/_tests

- name: Run code snippets in docs
if: ${{ matrix.rdkit == true && matrix.openeye == true }}
run: pytest -v --no-cov --doctest-glob="docs/*.rst" --doctest-glob="docs/*.md" docs/

- name: Run notebooks in docs
if: ${{ matrix.rdkit == true && matrix.openeye == true }}
run: python -m pytest -v --no-cov --nbval-lax docs/

- name: Run examples in docstrings
if: ${{ matrix.rdkit == true && matrix.openeye == true }}
run: |
pytest openff \
-v -x -n logical --no-cov --doctest-modules \
--ignore-glob='openff/toolkit/_tests*' \
--ignore=openff/toolkit/data/ \
--ignore=openff/toolkit/utils/utils.py

- name: Codecov
uses: codecov/codecov-action@v5
with:
Expand Down
18 changes: 17 additions & 1 deletion .github/workflows/examples.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ jobs:
name: ${{ matrix.os }}, Python ${{ matrix.python-version }}, RDKit=${{ matrix.rdkit }}, OpenEye=${{ matrix.openeye }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
python-version: ["3.11", "3.12"]
Expand Down Expand Up @@ -110,6 +109,23 @@ jobs:
- name: Shim for `pytest-xdist` + Pint cross-interaction
run: python -c "from openff.toolkit import *"

- name: Run code snippets in docs
if: ${{ matrix.openeye == true && matrix.rdkit == true }}
run: pytest -v --doctest-glob="docs/*.rst" --doctest-glob="docs/*.md" docs/

- name: Run notebooks in docs
if: ${{ matrix.openeye == true && matrix.rdkit == true }}
run: python -m pytest -v --nbval-lax docs/

- name: Run examples in docstrings
if: ${{ matrix.openeye == true && matrix.rdkit == true }}
run: |
pytest openff \
-v -x -n logical --doctest-modules \
--ignore-glob='openff/toolkit/_tests*' \
--ignore=openff/toolkit/data/ \
--ignore=openff/toolkit/utils/utils.py

- name: Run example scripts
run: |
if [[ ${{ matrix.rdkit }} == false ]]; then
Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ ci:
files: ^openff|(^examples/((?!deprecated).)*$)|^docs|(^utilities/((?!deprecated).)*$)
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.12.11
rev: v0.12.12
hooks:
- id: ruff-format
- id: ruff-check
Expand Down
2 changes: 1 addition & 1 deletion devtools/conda-envs/openeye.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ dependencies:
- mdtraj
- nglview
- parmed =4
- mypy =1.15
- mypy =1.17
- typing_extensions
- pip:
- types-setuptools
Expand Down
5 changes: 2 additions & 3 deletions devtools/conda-envs/rdkit-examples.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,8 @@ dependencies:
- typing_extensions
- nglview
# Toolkit-specific
- ambertools
# https://github.com/rdkit/rdkit/issues/7221 and https://github.com/rdkit/rdkit/issues/7583
- rdkit =2024
- ambertools =24
- rdkit =2025
# Test-only/optional/dev/typing/examples
- pytest =8
- pytest-xdist
Expand Down
14 changes: 11 additions & 3 deletions devtools/conda-envs/rdkit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ dependencies:
- openff-nagl-models >=0.3.0
- typing_extensions
# Toolkit-specific
- ambertools >=22
# https://github.com/rdkit/rdkit/issues/7221 and https://github.com/rdkit/rdkit/issues/7583
- rdkit !=2024.03.6,!=2024.03.5
- ambertools =24
- rdkit =2025
# Test-only/optional/dev/typing
- mypy =1.17
- pytest =8
- pytest-cov
- pytest-xdist
Expand All @@ -39,3 +39,11 @@ dependencies:
- qcportal >=0.50
- qcengine
- nglview
- pip:
- types-setuptools
- types-toml
- types-PyYAML
- types-networkx
- types-xmltodict
- types-cachetools
- mongo-types
17 changes: 11 additions & 6 deletions devtools/conda-envs/test_env.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,12 @@ dependencies:
- openff-nagl-models >=0.3.0
- typing_extensions
# Toolkit-specific
- ambertools >=22
# rdkit 2024.03.6 and 2024.09.1 packages fail when run natively on osx-arm64 macs
# https://github.com/rdkit/rdkit/issues/7583
- rdkit !=2024.03.6,!=2024.09.1
- ambertools =24
- rdkit =2025

- openeye-toolkits
# Test-only/optional/dev/typing
- mypy =1.17
- pytest =8
- pytest-cov
- pytest-xdist
Expand All @@ -46,5 +45,11 @@ dependencies:
- nglview
- mdtraj
- nbval
# No idea why this is necessary, see https://github.com/openforcefield/openff-toolkit/pull/1821
- nomkl
- pip:
- types-setuptools
- types-toml
- types-PyYAML
- types-networkx
- types-xmltodict
- types-cachetools
- mongo-types
2 changes: 1 addition & 1 deletion openff/toolkit/topology/_mm_molecule.py
Original file line number Diff line number Diff line change
Expand Up @@ -505,7 +505,7 @@ def node_match_func(node1, node2):
else:
return False, None

def generate_unique_atom_names(self):
def generate_unique_atom_names(self) -> None:
"""Generate unique atom names. See `Molecule.generate_unique_atom_names`."""
from collections import defaultdict

Expand Down
38 changes: 20 additions & 18 deletions openff/toolkit/topology/molecule.py
Original file line number Diff line number Diff line change
Expand Up @@ -1250,8 +1250,8 @@ def ordered_connection_table_hash(self) -> int:
for bond in self.bonds:
id += f"{bond.bond_order}_{bond.stereochemistry}_{bond.atom1_index}_{bond.atom2_index}__"

self._ordered_connection_table_hash = hashlib.sha3_224(id.encode("utf-8")).hexdigest()
return self._ordered_connection_table_hash
self._ordered_connection_table_hash = hashlib.sha3_224(id.encode("utf-8")).hexdigest() # type: ignore[assignment]
return self._ordered_connection_table_hash # type: ignore[return-value]

@classmethod
def from_dict(cls: type[FM], molecule_dict: dict) -> FM:
Expand Down Expand Up @@ -1350,7 +1350,7 @@ def __repr__(self) -> str:
hill = self.to_hill_formula()
return description + f" with bad SMILES and Hill formula '{hill}'"

def _initialize(self):
def _initialize(self) -> None:
"""
Clear the contents of the current molecule.
"""
Expand All @@ -1359,9 +1359,9 @@ def _initialize(self):
self._bonds: list[Bond] = list() # list of bonds between Atom objects
self._properties = {} # Attached properties to be preserved
# self._cached_properties = None # Cached properties (such as partial charges) can be recomputed as needed
self._partial_charges = None
self._conformers = None # Optional conformers
self._hill_formula = None # Cached Hill formula
self._partial_charges: Quantity | None = None
self._conformers: list[Quantity] | None = None # Optional conformers
self._hill_formula: str | None = None # Cached Hill formula
self._hierarchy_schemes = dict()
self._ordered_connection_table_hash = None
self._invalidate_cached_properties()
Expand Down Expand Up @@ -2771,7 +2771,7 @@ def assign_fractional_bond_orders(
f"Expected ToolkitRegistry or ToolkitWrapper. Got {type(toolkit_registry)}."
)

def _invalidate_cached_properties(self):
def _invalidate_cached_properties(self) -> None:
"""
Indicate that the chemical entity has been altered.

Expand Down Expand Up @@ -4095,7 +4095,12 @@ def title(frame):
# now close the file
xyz_data.close()

def to_file(self, file_path, file_format, toolkit_registry=GLOBAL_TOOLKIT_REGISTRY):
def to_file(
self,
file_path,
file_format: str,
toolkit_registry: TKR = GLOBAL_TOOLKIT_REGISTRY,
):
"""Write the current molecule to a file or file-like object

Parameters
Expand Down Expand Up @@ -4123,14 +4128,11 @@ def to_file(self, file_path, file_format, toolkit_registry=GLOBAL_TOOLKIT_REGIST
>>> molecule.to_file('imatinib.pdb', file_format='pdb') # doctest: +SKIP

"""
toolkit: ToolkitRegistry | None

if isinstance(toolkit_registry, ToolkitRegistry):
pass
elif isinstance(toolkit_registry, ToolkitWrapper):
toolkit = toolkit_registry
toolkit_registry = ToolkitRegistry(toolkit_precedence=[])
toolkit_registry.add_toolkit(toolkit)
# make the toolkit_registry: ToolkitWrapper actually a ToolkitRegistry
toolkit_registry = ToolkitRegistry(toolkit_precedence=[toolkit_registry]) # type: ignore[list-item]
else:
raise InvalidToolkitRegistryError(
"'toolkit_registry' must be either a ToolkitRegistry or a ToolkitWrapper"
Expand All @@ -4142,10 +4144,10 @@ def to_file(self, file_path, file_format, toolkit_registry=GLOBAL_TOOLKIT_REGIST
return self._to_xyz_file(file_path=file_path)

# Take the first toolkit that can write the desired output format
toolkit = None
toolkit: ToolkitRegistry | None = None
for query_toolkit in toolkit_registry.registered_toolkits:
if file_format in query_toolkit.toolkit_file_write_formats:
toolkit = query_toolkit
toolkit = query_toolkit # type: ignore[assignment]
break

# Raise an exception if no toolkit was found to provide the requested file_format
Expand All @@ -4159,9 +4161,9 @@ def to_file(self, file_path, file_format, toolkit_registry=GLOBAL_TOOLKIT_REGIST
)

if isinstance(file_path, str | pathlib.Path):
toolkit.to_file(self, file_path, file_format)
toolkit.to_file(self, file_path, file_format) # type: ignore[attr-defined]
else:
toolkit.to_file_obj(self, file_path, file_format)
toolkit.to_file_obj(self, file_path, file_format) # type: ignore[attr-defined]

def enumerate_tautomers(self, max_states=20, toolkit_registry=GLOBAL_TOOLKIT_REGISTRY):
"""
Expand Down Expand Up @@ -5815,7 +5817,7 @@ def to_dict(self) -> dict:
return_dict["hierarchy_elements"] = [e.to_dict() for e in self.hierarchy_elements]
return return_dict

def perceive_hierarchy(self):
def perceive_hierarchy(self) -> None:
"""
Prepare the parent ``Molecule`` for iteration according to this scheme.

Expand Down
14 changes: 8 additions & 6 deletions openff/toolkit/topology/topology.py
Original file line number Diff line number Diff line change
Expand Up @@ -1299,7 +1299,7 @@ def _initialize_from_dict(self, topology_dict):

@staticmethod
@requires_package("openmm")
def _openmm_topology_to_networkx(openmm_topology):
def _openmm_topology_to_networkx(openmm_topology: "openmm.app.Topology"):
import networkx as nx

# Convert all openMM mols to graphs
Expand Down Expand Up @@ -1763,8 +1763,8 @@ def from_pdb(
off_atom.metadata["chain_id"] = atom.residue.chain.id
off_atom.name = atom.name

for offmol in topology.molecules:
offmol.add_default_hierarchy_schemes()
for offmol in topology.molecules: # type: ignore[operator]
offmol.add_default_hierarchy_schemes() # type: ignore[operator]

return topology

Expand Down Expand Up @@ -2104,9 +2104,11 @@ def set_positions(self, array: Quantity) -> None:
raise IncompatibleUnitError("array should be an OpenFF Quantity with dimensions of length")

# Copy the array in nanometers and make it an OpenFF Quantity
array = Quantity(np.asarray(array.to(unit.nanometer).magnitude), unit.nanometer)
if array.shape != (self.n_atoms, 3):
raise WrongShapeError(f"Array has shape {array.shape} but should have shape {self.n_atoms, 3}")
array = Quantity(np.asarray(array.to(unit.nanometer).magnitude), unit.nanometer) # type: ignore[attr-defined]
if array.shape != (self.n_atoms, 3): # type: ignore[attr-defined]
raise WrongShapeError(
f"Array has shape {array.shape} but should have shape {self.n_atoms, 3}" # type: ignore[attr-defined]
)

start = 0
for molecule in self.molecules:
Expand Down
2 changes: 1 addition & 1 deletion openff/toolkit/typing/engines/smirnoff/forcefield.py
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ def __init__(
# Parse all sources containing SMIRNOFF parameter definitions
self.parse_sources(sources, allow_cosmetic_attributes=allow_cosmetic_attributes)

def _initialize(self):
def _initialize(self) -> None:
"""
Initialize all object fields.
"""
Expand Down
2 changes: 1 addition & 1 deletion openff/toolkit/utils/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ def quantity_to_string(input_quantity: Quantity) -> str:
if isinstance(unitless_value, np.ndarray):
unitless_value = list(unitless_value)

unit_string = unit_to_string(input_quantity.units)
unit_string = unit_to_string(input_quantity.units) # type: ignore

return f"{unitless_value} * {unit_string}"

Expand Down
11 changes: 9 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ exclude_lines = [

[tool.mypy]
python_version = 3.12
plugins = "numpy.typing.mypy_plugin"
warn_unused_configs = true
# would be nice to flip back to true
warn_unused_ignores = false
Expand All @@ -79,7 +78,7 @@ module = [
"openmm",
"openmm.app",
"openmm.unit",
"rdkit",
"rdkit", # maybe revisit if/when stubs are fixed
"rdkit.Chem",
"rdkit.Chem.Draw",
"rdkit.DataStructs.cDataStructs",
Expand All @@ -93,3 +92,11 @@ module = [
"constraint",
]
ignore_missing_imports = true

[[tool.mypy.overrides]]
# bit of a hack to prevent crash
module=[
"rdkit"
]
follow_imports = "skip"
follow_imports_for_stubs = true