diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 089bfb92d..f8f51a3fd 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -14,16 +14,12 @@ jobs: - uses: actions/setup-python@v5 with: - python-version: '3.10' + python-version: '3.12' - - name: Install linters + - name: Install ruff run: | - pip install black isort + pip install ruff - - name: Run black + - name: Run ruffblack run: | - black --check openff - - - name: Run isort - run: | - isort --check openff + ruff check . diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 95301fe68..e0b5de0f6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,35 +2,10 @@ ci: autoupdate_schedule: "quarterly" files: ^openff|(^examples/((?!deprecated).)*$)|^docs|(^utilities/((?!deprecated).)*$) repos: -- repo: https://github.com/psf/black - rev: 24.4.2 +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.6.5 hooks: - - id: black - - id: black-jupyter -- repo: https://github.com/PyCQA/isort - rev: 5.13.2 - hooks: - - id: isort -- repo: https://github.com/PyCQA/flake8 - rev: 7.1.0 - hooks: - - id: flake8 - files: ^openff|(^utilities/((?!deprecated).)*$) - additional_dependencies: [ - 'flake8-absolute-import', - 'flake8-no-pep420', - 'flake8-bugbear', - ] -- repo: https://github.com/nbQA-dev/nbQA - rev: 1.8.5 - hooks: - - id: nbqa-pyupgrade - args: - - --py39-plus - - id: nbqa-isort - - id: nbqa-flake8 - args: - - '--select=F' + - id: ruff - repo: https://github.com/adamchainz/blacken-docs rev: 1.18.0 hooks: diff --git a/devtools/scripts/build_cookiecutter_json.py b/devtools/scripts/build_cookiecutter_json.py index a649ec7e9..85bb7f62c 100644 --- a/devtools/scripts/build_cookiecutter_json.py +++ b/devtools/scripts/build_cookiecutter_json.py @@ -1,6 +1,5 @@ -import sys import json - +import sys release_tag = sys.argv[1] python_version = sys.argv[2] diff --git a/docs/conf.py b/docs/conf.py index d82d67c0a..592602691 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- # # openff-toolkit documentation build configuration file, created by # sphinx-quickstart on Sun Dec 3 23:12:54 2017. @@ -19,12 +18,12 @@ # import os import sys +from importlib.util import find_spec as find_import_spec + +import openff.toolkit sys.path.insert(0, os.path.abspath(".")) -import sphinx - -import openff.toolkit # -- General configuration ------------------------------------------------ @@ -128,8 +127,6 @@ # sphinx-notfound-page # https://github.com/readthedocs/sphinx-notfound-page # Renders a 404 page with absolute links -from importlib.util import find_spec as find_import_spec - if find_import_spec("notfound"): extensions.append("notfound.extension") @@ -292,7 +289,10 @@ "OpenFF Toolkit Documentation", author, "openff-toolkit", - "A modern, extensible library for molecular mechanics force field science from the Open Force Field Consortium.", + ( + "A modern, extensible library for molecular mechanics force field science from the Open Force " + "Field Consortium.", + ), "Miscellaneous", ), ] diff --git a/docs/users/molecule_cookbook.ipynb b/docs/users/molecule_cookbook.ipynb index dbf0b1025..bd0c9e588 100644 --- a/docs/users/molecule_cookbook.ipynb +++ b/docs/users/molecule_cookbook.ipynb @@ -44,12 +44,12 @@ "source": [ "# Workaround for https://github.com/conda-forge/qcfractal-feedstock/issues/43\n", "try:\n", - " import qcportal # noqa\n", + " import qcportal # noqa: RUF100\n", "except ImportError:\n", " pass\n", "import sys\n", "\n", - "ipython = get_ipython() # noqa\n", + "ipython = get_ipython() # noqa: RUF100\n", "\n", "\n", "def hide_traceback(\n", @@ -1413,9 +1413,9 @@ } ], "source": [ - "from openmm.app.pdbfile import PDBFile\n", + "import openmm.app\n", "\n", - "openmm_topology = PDBFile(\"zw_l_alanine.pdb\").getTopology()\n", + "openmm_topology = openmm.app.PDBFile(\"zw_l_alanine.pdb\").getTopology()\n", "openff_topology = Topology.from_openmm(openmm_topology, unique_molecules=[zw_l_alanine])\n", "\n", "from_openmm_topology = openff_topology.molecule(0)\n", @@ -1492,9 +1492,9 @@ } ], "source": [ - "from mdtraj import load_pdb\n", + "import mdtraj\n", "\n", - "mdtraj_topology = load_pdb(\"zw_l_alanine.pdb\").topology\n", + "mdtraj_topology = mdtraj.load_pdb(\"zw_l_alanine.pdb\").topology\n", "openff_topology = Topology.from_mdtraj(mdtraj_topology, unique_molecules=[zw_l_alanine])\n", "\n", "from_mdtraj_topology = openff_topology.molecule(0)\n", diff --git a/docs/users/pdb_cookbook/index.ipynb b/docs/users/pdb_cookbook/index.ipynb index 549b55976..73d5531ef 100644 --- a/docs/users/pdb_cookbook/index.ipynb +++ b/docs/users/pdb_cookbook/index.ipynb @@ -67,10 +67,11 @@ "outputs": [], "source": [ "import sys\n", + "import warnings\n", "\n", "from openff.toolkit import Molecule, Topology\n", "\n", - "ipython = get_ipython() # noqa\n", + "ipython = get_ipython()\n", "\n", "\n", "def hide_traceback(\n", @@ -93,8 +94,6 @@ "ipython.showtraceback = hide_traceback\n", "\n", "# Hide NumPy warnings\n", - "import warnings\n", - "\n", "warnings.filterwarnings(\n", " \"ignore\",\n", " r\"The value of the smallest subnormal for \",\n", diff --git a/examples/conformer_energies/conformer_energies.py b/examples/conformer_energies/conformer_energies.py index c0f1a3297..dc0a64301 100644 --- a/examples/conformer_energies/conformer_energies.py +++ b/examples/conformer_energies/conformer_energies.py @@ -144,7 +144,11 @@ def compute_conformer_energies_from_file(filename): if __name__ == "__main__": parser = argparse.ArgumentParser( - description="Perform energy minimization on a molecule, potentially with many conformers. For each conformer, this script will provide the initial energy, minimized energy, and RMSD between the initial and minimized structure (both as STDOUT and a csv file). The minimized conformers will be written out to SDF." + description=( + "Perform energy minimization on a molecule, potentially with many conformers. For each conformer, this " + "script will provide the initial energy, minimized energy, and RMSD between the initial and minimized " + "structure (both as STDOUT and a csv file). The minimized conformers will be written out to SDF." + ), ) parser.add_argument( "-f", "--filename", help="Name of an input file containing conformers" diff --git a/examples/external/swap_existing_ligand_parameters_with_openmmforcefields/swap_existing_ligand_parameters_with_openmmforcefields.ipynb b/examples/external/swap_existing_ligand_parameters_with_openmmforcefields/swap_existing_ligand_parameters_with_openmmforcefields.ipynb index a24250121..e6dade67d 100644 --- a/examples/external/swap_existing_ligand_parameters_with_openmmforcefields/swap_existing_ligand_parameters_with_openmmforcefields.ipynb +++ b/examples/external/swap_existing_ligand_parameters_with_openmmforcefields/swap_existing_ligand_parameters_with_openmmforcefields.ipynb @@ -136,7 +136,8 @@ } ], "source": [ - "# Create a parameterized OpenMM System from the PDB topology without bond constraints so we can convert to other packages\n", + "# Create a parameterized OpenMM System from the PDB topology without bond constraints\n", + "# so we can convert to other packages\n", "system = forcefield.createSystem(\n", " pdbfile.topology,\n", " nonbondedMethod=app.PME,\n", diff --git a/examples/external/using_smirnoff_with_amber_protein_forcefield/BRD4_inhibitor_benchmark.ipynb b/examples/external/using_smirnoff_with_amber_protein_forcefield/BRD4_inhibitor_benchmark.ipynb index f356c1877..f6bf16445 100644 --- a/examples/external/using_smirnoff_with_amber_protein_forcefield/BRD4_inhibitor_benchmark.ipynb +++ b/examples/external/using_smirnoff_with_amber_protein_forcefield/BRD4_inhibitor_benchmark.ipynb @@ -19,7 +19,10 @@ "source": [ "# Retrieve protein and ligand files for BRD4 and a docked inhibitor from the benchmark systems GitHub repository\n", "# https://github.com/MobleyLab/benchmarksets\n", + "import parmed\n", "import requests\n", + "from openmm import XmlSerializer, app, unit\n", + "from openmm.app import HBonds, NoCutoff, PDBFile\n", "\n", "repo_url = (\n", " \"https://raw.githubusercontent.com/MobleyLab/benchmarksets/master/input_files/\"\n", @@ -92,16 +95,6 @@ "" ] }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "from openmm import XmlSerializer, app, unit\n", - "from openmm.app import HBonds, NoCutoff, PDBFile" - ] - }, { "cell_type": "code", "execution_count": 4, @@ -120,8 +113,6 @@ "ligand_pdbfile = PDBFile(\"ligand.pdb\")\n", "\n", "# Convert OpenMM System object containing ligand parameters into a ParmEd Structure.\n", - "import parmed\n", - "\n", "ligand_structure = parmed.openmm.load_topology(\n", " ligand_pdbfile.topology, ligand_system, xyz=ligand_pdbfile.positions\n", ")" diff --git a/examples/forcefield_modification/forcefield_modification.ipynb b/examples/forcefield_modification/forcefield_modification.ipynb index 3dfd5c081..80b65b830 100644 --- a/examples/forcefield_modification/forcefield_modification.ipynb +++ b/examples/forcefield_modification/forcefield_modification.ipynb @@ -1574,8 +1574,8 @@ } ], "source": [ - "fluorine = [atom for atom in ligand.atoms if atom.symbol == \"F\"][0]\n", - "carbon = [neighbor_atom for neighbor_atom in fluorine.bonded_atoms][0]\n", + "fluorine = next(atom for atom in ligand.atoms if atom.symbol == \"F\")\n", + "carbon = next(iter(fluorine.bonded_atoms))\n", "fluorine_carbon_bond_indices = (\n", " fluorine.molecule_atom_index,\n", " carbon.molecule_atom_index,\n", diff --git a/examples/virtual_sites/vsite_showcase.ipynb b/examples/virtual_sites/vsite_showcase.ipynb index 437cba93a..8b68a901c 100644 --- a/examples/virtual_sites/vsite_showcase.ipynb +++ b/examples/virtual_sites/vsite_showcase.ipynb @@ -795,7 +795,7 @@ "_energy_difference = from_openmm(energy_difference)\n", "\n", "print(\n", - " f\"Results for: OpenFF – OpenMM comparison (per molecule) \\n\"\n", + " f\"Results for: OpenFF – OpenMM comparison (per molecule) \\n\" # noqa: RUF001\n", " f\"Energy difference ({_energy_difference.units}):\\n\\t\"\n", " f\"{_energy_difference.m:0.3e}\\n\"\n", " f\"Coordinates difference ({coordinate_difference.units}, norm):\\n\\t\"\n", diff --git a/examples/visualization/visualization.ipynb b/examples/visualization/visualization.ipynb index 31b5c8fa6..ca415078c 100644 --- a/examples/visualization/visualization.ipynb +++ b/examples/visualization/visualization.ipynb @@ -66,7 +66,8 @@ }, "outputs": [], "source": [ - "from openff.toolkit import Molecule, Topology" + "from openff.toolkit import Molecule, Topology\n", + "from openff.toolkit.utils import get_data_file_path" ] }, { @@ -225,7 +226,7 @@ } ], "source": [ - "display(m) # noqa" + "display(m)" ] }, { @@ -733,7 +734,6 @@ }, "outputs": [], "source": [ - "from openff.toolkit.utils.utils import get_data_file_path\n", "\n", "topology = Topology.from_pdb(\n", " get_data_file_path(\"systems/test_systems/T4_lysozyme_water_ions.pdb\")\n", diff --git a/openff/toolkit/_tests/_stale_tests.py b/openff/toolkit/_tests/_stale_tests.py index 5bdc7d445..e44684c62 100644 --- a/openff/toolkit/_tests/_stale_tests.py +++ b/openff/toolkit/_tests/_stale_tests.py @@ -114,9 +114,7 @@ def test_electrostatics_options(self): # Change electrostatics method forcefield.forces["Electrostatics"].method = method f = partial(check_system_creation_from_molecule, forcefield, molecule) - f.description = "Testing {} parameter assignment using molecule {}".format( - offxml_file_path, molecule.name - ) + f.description = f"Testing {offxml_file_path} parameter assignment using molecule {molecule.name}" # yield f # TODO: Implement a similar test, where we compare OpenMM energy evals from an # AMBER-parameterized system to OFF-parameterized systems diff --git a/openff/toolkit/_tests/create_molecules.py b/openff/toolkit/_tests/create_molecules.py index 8084d1bab..370133c63 100644 --- a/openff/toolkit/_tests/create_molecules.py +++ b/openff/toolkit/_tests/create_molecules.py @@ -419,8 +419,8 @@ def propane_from_smiles_w_vsites(): def tip5_water(): # Make a TIP5 water molecule = Molecule.from_smiles("[H][O][H]") - O1 = [atom for atom in molecule.atoms if atom.atomic_number == 8][0] - H1, H2 = [atom for atom in O1.bonded_atoms if atom.atomic_number == 1] + O1 = next(atom for atom in molecule.atoms if atom.atomic_number == 8) + H1, H2 = (atom for atom in O1.bonded_atoms if atom.atomic_number == 1) molecule.add_divalent_lone_pair_virtual_site( (H1, O1, H2), 0.7 * unit.angstrom, diff --git a/openff/toolkit/_tests/test_energies.py b/openff/toolkit/_tests/test_energies.py index 2ece6424d..d2f382da5 100644 --- a/openff/toolkit/_tests/test_energies.py +++ b/openff/toolkit/_tests/test_energies.py @@ -26,7 +26,7 @@ def test_reference(constrained, mol): by version 0.8.0 of the toolkit""" # TODO: Also test periodic vs. vacuum with open( - get_data_file_path("reference_energies/reference_after_1007.json"), "r" + get_data_file_path("reference_energies/reference_after_1007.json") ) as fi: reference = json.loads(fi.read()) diff --git a/openff/toolkit/_tests/test_examples.py b/openff/toolkit/_tests/test_examples.py index 34170601c..221335b0e 100644 --- a/openff/toolkit/_tests/test_examples.py +++ b/openff/toolkit/_tests/test_examples.py @@ -91,7 +91,7 @@ def find_readme_examples() -> list[str]: if readme_file_path is None: return list() - with open(readme_file_path, "r") as f: + with open(readme_file_path) as f: readme_content = f.read() return re.findall("```python(.*?)```", readme_content, flags=re.DOTALL) diff --git a/openff/toolkit/_tests/test_forcefield.py b/openff/toolkit/_tests/test_forcefield.py index 278ea6c4c..5a3cd4e98 100644 --- a/openff/toolkit/_tests/test_forcefield.py +++ b/openff/toolkit/_tests/test_forcefield.py @@ -1934,9 +1934,9 @@ def test_charges_from_molecule(self, toolkit_registry, force_field): omm_system = force_field.create_openmm_system( topology, charge_from_molecules=molecules, toolkit_registry=toolkit_registry ) - nonbondedForce = [ + nonbondedForce = next( f for f in omm_system.getForces() if type(f) is NonbondedForce - ][0] + ) expected_charges = ( (0, -0.4 * openmm_unit.elementary_charge), (1, -0.3 * openmm_unit.elementary_charge), @@ -1963,9 +1963,9 @@ def test_charges_from_molecule_reordered(self, toolkit_registry, force_field): charge_from_molecules=molecules, toolkit_registry=toolkit_registry, ) - nonbondedForce = [ + nonbondedForce = next( f for f in omm_system.getForces() if type(f) is NonbondedForce - ][0] + ) expected_charges = ( (0, -0.2 * openmm_unit.elementary_charge), (1, -0.4 * openmm_unit.elementary_charge), @@ -2032,9 +2032,9 @@ def test_some_charges_from_molecule(self, toolkit_registry, force_field): omm_system = force_field.create_openmm_system( topology, charge_from_molecules=[ethanol], toolkit_registry=toolkit_registry ) - nonbondedForce = [ + nonbondedForce = next( f for f in omm_system.getForces() if type(f) is NonbondedForce - ][0] + ) expected_charges = ( (18, -0.4 * openmm_unit.elementary_charge), (19, -0.3 * openmm_unit.elementary_charge), @@ -2064,9 +2064,9 @@ def test_library_charges_to_single_water(self, ff_inputs): get_data_file_path(os.path.join("systems", "monomers", "water.sdf")) ) omm_system = ff.create_openmm_system(mol.to_topology()) - nonbondedForce = [ + nonbondedForce = next( f for f in omm_system.getForces() if type(f) is NonbondedForce - ][0] + ) expected_charges = [-0.834, 0.417, 0.417] * openmm_unit.elementary_charge for particle_index, expected_charge in enumerate(expected_charges): q, _, _ = nonbondedForce.getParticleParameters(particle_index) @@ -2092,11 +2092,11 @@ def test_charge_increment_model_forward_and_reverse_ethanol(self): del ff._parameter_handlers["ToolkitAM1BCC"] top = Topology.from_molecules([create_ethanol(), create_reversed_ethanol()]) sys = ff.create_openmm_system(top) - nonbonded_force = [ + nonbonded_force = next( force for force in sys.getForces() if isinstance(force, openmm.NonbondedForce) - ][0] + ) expected_charges = [ 0.2, -0.15, @@ -2140,16 +2140,16 @@ def test_charge_increment_model_one_less_ci_than_tagged_atom(self): sys1 = ff2.create_openmm_system(top) sys2 = ff2.create_openmm_system(top) # Extract the nonbonded force from each system - nonbonded_force1 = [ + nonbonded_force1 = next( force for force in sys1.getForces() if isinstance(force, openmm.NonbondedForce) - ][0] - nonbonded_force2 = [ + ) + nonbonded_force2 = next( force for force in sys2.getForces() if isinstance(force, openmm.NonbondedForce) - ][0] + ) # Ensure that the systems both have the correct charges assigned expected_charges = [ @@ -2226,11 +2226,11 @@ def test_charge_increment_model_net_charge(self): top = acetate.to_topology() sys = ff.create_openmm_system(top) - nonbonded_force = [ + nonbonded_force = next( force for force in sys.getForces() if isinstance(force, openmm.NonbondedForce) - ][0] + ) expected_charges = [ 0, 0.15, @@ -2258,11 +2258,11 @@ def test_charge_increment_model_deduplicate_symmetric_matches(self): del ff._parameter_handlers["ToolkitAM1BCC"] sys = ff.create_openmm_system(top) - nonbonded_force = [ + nonbonded_force = next( force for force in sys.getForces() if isinstance(force, openmm.NonbondedForce) - ][0] + ) expected_charges = [ 0.3, 0, @@ -2286,11 +2286,11 @@ def test_charge_increment_model_deduplicate_symmetric_matches(self): del ff._parameter_handlers["ToolkitAM1BCC"] sys = ff.create_openmm_system(top) - nonbonded_force = [ + nonbonded_force = next( force for force in sys.getForces() if isinstance(force, openmm.NonbondedForce) - ][0] + ) expected_charges = [ 0.3, 0, @@ -2314,11 +2314,11 @@ def test_charge_increment_model_deduplicate_symmetric_matches(self): del ff._parameter_handlers["ToolkitAM1BCC"] sys = ff.create_openmm_system(top) - nonbonded_force = [ + nonbonded_force = next( force for force in sys.getForces() if isinstance(force, openmm.NonbondedForce) - ][0] + ) expected_charges = [ 0.3, 0, @@ -2346,11 +2346,11 @@ def test_charge_increment_model_completely_overlapping_matches_override(self): ethanol = create_ethanol() top = ethanol.to_topology() sys = ff.create_openmm_system(top) - nonbonded_force = [ + nonbonded_force = next( force for force in sys.getForces() if isinstance(force, openmm.NonbondedForce) - ][0] + ) expected_charges = [ 0.3, 0, @@ -2378,11 +2378,11 @@ def test_charge_increment_model_partially_overlapping_matches_both_apply(self): ethanol = create_ethanol() top = ethanol.to_topology() sys = ff.create_openmm_system(top) - nonbonded_force = [ + nonbonded_force = next( force for force in sys.getForces() if isinstance(force, openmm.NonbondedForce) - ][0] + ) expected_charges = [ 0.35, -0.05, @@ -2446,9 +2446,9 @@ def test_library_charge_hierarchy(self): get_data_file_path(os.path.join("systems", "monomers", "water.sdf")) ) omm_system = ff.create_openmm_system(mol.to_topology()) - nonbondedForce = [ + nonbondedForce = next( f for f in omm_system.getForces() if type(f) is NonbondedForce - ][0] + ) expected_charges = [-2.0, 1.0, 1.0] * openmm_unit.elementary_charge for particle_index, expected_charge in enumerate(expected_charges): q, _, _ = nonbondedForce.getParticleParameters(particle_index) @@ -2461,9 +2461,9 @@ def test_library_charge_hierarchy(self): get_data_file_path("test_forcefields/tip3p.offxml"), ) omm_system = ff.create_openmm_system(mol.to_topology()) - nonbondedForce = [ + nonbondedForce = next( f for f in omm_system.getForces() if type(f) is NonbondedForce - ][0] + ) expected_charges = [-0.834, 0.417, 0.417] * openmm_unit.elementary_charge for particle_index, expected_charge in enumerate(expected_charges): q, _, _ = nonbondedForce.getParticleParameters(particle_index) @@ -2480,9 +2480,9 @@ def test_library_charges_to_two_waters(self): ) top = Topology.from_molecules([mol, mol]) omm_system = ff.create_openmm_system(top) - nonbondedForce = [ + nonbondedForce = next( f for f in omm_system.getForces() if type(f) is NonbondedForce - ][0] + ) expected_charges = [ -0.834, 0.417, @@ -2533,9 +2533,9 @@ def test_library_charges_to_three_ethanols_different_atom_ordering(self): ] top = Topology.from_molecules(molecules) omm_system = ff.create_openmm_system(top) - nonbondedForce = [ + nonbondedForce = next( f for f in omm_system.getForces() if type(f) is NonbondedForce - ][0] + ) expected_charges = [ -0.2, -0.1, @@ -2576,12 +2576,12 @@ def test_library_charges_monatomic_ions(self, monatomic_ion, formal_charge): get_data_file_path("test_forcefields/test_forcefield.offxml"), get_data_file_path("test_forcefields/ion_charges.offxml"), ) - mol = Molecule.from_smiles("[{}]".format(monatomic_ion)) + mol = Molecule.from_smiles(f"[{monatomic_ion}]") omm_system = ff.create_openmm_system(mol.to_topology()) - nonbondedForce = [ + nonbondedForce = next( f for f in omm_system.getForces() if type(f) is NonbondedForce - ][0] + ) q, _, _ = nonbondedForce.getParticleParameters(0) assert q == formal_charge @@ -2759,9 +2759,9 @@ def test_assign_charges_to_molecule_in_parts_using_multiple_library_charges(self ] top = Topology.from_molecules(molecules) omm_system = ff.create_openmm_system(top) - nonbondedForce = [ + nonbondedForce = next( f for f in omm_system.getForces() if type(f) is NonbondedForce - ][0] + ) expected_charges = [ -0.2, -0.1, @@ -2801,9 +2801,9 @@ def test_assign_charges_using_library_charges_by_single_atoms(self): ] top = Topology.from_molecules(molecules) omm_system = ff.create_openmm_system(top) - nonbondedForce = [ + nonbondedForce = next( f for f in omm_system.getForces() if type(f) is NonbondedForce - ][0] + ) expected_charges = [ -0.2, -0.1, @@ -2854,9 +2854,9 @@ def test_library_charges_dont_parameterize_molecule_because_of_incomplete_covera xml_ethanol_library_charges_by_atom_ff, ) omm_system = ff.create_openmm_system(top) - nonbondedForce = [ + nonbondedForce = next( f for f in omm_system.getForces() if type(f) is NonbondedForce - ][0] + ) for particle_index in range(top.n_atoms): q, _, _ = nonbondedForce.getParticleParameters(particle_index) assert q != 0 * unit.elementary_charge @@ -3003,7 +3003,7 @@ def extract_id(file_path): # Remove fast test cases from slow ones to avoid duplicate tests. # Remove also water (c1302), which was reparameterized in AlkEthOH # to be TIP3P (not covered by Frosst_AlkEthOH_parmAtFrosst. - for fast_test_case in fast_test_cases + ["c1302"]: + for fast_test_case in [*fast_test_cases, 'c1302']: slow_test_cases.remove(fast_test_case) # Mark all slow cases as slow. @@ -3237,7 +3237,7 @@ def test_freesolv_parameters_assignment( ff_system = ff.create_openmm_system(molecule.to_topology()) # Load OpenMM System created with the 0.1 version of the toolkit. - with open(xml_file_path, "r") as f: + with open(xml_file_path) as f: xml_system = openmm.XmlSerializer.deserialize(f.read()) # Compare parameters. We ignore the improper folds as in 0.0.3 we @@ -3322,11 +3322,11 @@ def test_freesolv_gbsa_energies( off_top, charge_from_molecules=[molecule] ) - off_nonbonded_force = [ + off_nonbonded_force = next( force for force in off_omm_system.getForces() if isinstance(force, openmm.NonbondedForce) - ][0] + ) omm_top = off_top.to_openmm() pmd_struct = pmd.openmm.load_topology(omm_top, off_omm_system, positions) @@ -3906,11 +3906,11 @@ def test_fractional_bondorder_from_molecule( ) # Verify that the assigned bond parameters were correctly interpolated - off_bond_force = [ + off_bond_force = next( force for force in omm_system.getForces() if isinstance(force, openmm.HarmonicBondForce) - ][0] + ) for idx in range(off_bond_force.getNumBonds()): params = off_bond_force.getBondParameters(idx) @@ -3927,11 +3927,11 @@ def test_fractional_bondorder_from_molecule( assert_almost_equal(length / length.unit, length_bond_interpolated) # Verify that the assigned torsion parameters were correctly interpolated - off_torsion_force = [ + off_torsion_force = next( force for force in omm_system.getForces() if isinstance(force, openmm.PeriodicTorsionForce) - ][0] + ) for idx in range(off_torsion_force.getNumTorsions()): params = off_torsion_force.getTorsionParameters(idx) @@ -3992,11 +3992,11 @@ def test_fractional_bondorder_superseded_by_standard_torsion( partial_bond_orders_from_molecules=[mol], ) - off_torsion_force = [ + off_torsion_force = next( force for force in omm_system.getForces() if isinstance(force, openmm.PeriodicTorsionForce) - ][0] + ) for idx in range(off_torsion_force.getNumTorsions()): params = off_torsion_force.getTorsionParameters(idx) @@ -4141,7 +4141,7 @@ def test_read_smirnoff_spec_freesolv( ff_system = ff.create_openmm_system(molecule.to_topology()) # Load OpenMM System created with the 0.1 version of the toolkit. - with open(xml_file_path, "r") as f: + with open(xml_file_path) as f: xml_system = openmm.XmlSerializer.deserialize(f.read()) # Compare parameters. We ignore the improper folds as in 0.0.3 we @@ -4163,9 +4163,9 @@ class TestForceFieldGetPartialCharges(_ForceFieldFixtures): def get_partial_charges_from_create_openmm_system(mol, force_field): """Helper method to compute partial charges from a generated openmm System.""" system = force_field.create_openmm_system(mol.to_topology()) - nbforce = [ + nbforce = next( f for f in system.getForces() if isinstance(f, openmm.openmm.NonbondedForce) - ][0] + ) n_particles = nbforce.getNumParticles() charges = [nbforce.getParticleParameters(i)[0] for i in range(n_particles)] diff --git a/openff/toolkit/_tests/test_links.py b/openff/toolkit/_tests/test_links.py index 6cc2e38c2..bd994c11c 100644 --- a/openff/toolkit/_tests/test_links.py +++ b/openff/toolkit/_tests/test_links.py @@ -20,7 +20,7 @@ def find_readme_links() -> list[str]: return list() else: - with open(readme_file_path.as_posix(), "r") as f: + with open(readme_file_path.as_posix()) as f: readme_content = f.read() return re.findall("http[s]?://(?:[0-9a-zA-Z]|[-/.%:_])+", readme_content) @@ -56,7 +56,7 @@ def test_readme_links(readme_link): # Try to connect 5 times, keeping track of exceptions so useful feedback can be provided. success = False exception = None - for retry in range(5): # noqa: B007 + for retry in range(5): try: urlopen(request) success = True diff --git a/openff/toolkit/_tests/test_mm_molecule.py b/openff/toolkit/_tests/test_mm_molecule.py index a52b79e94..8f9a41c42 100644 --- a/openff/toolkit/_tests/test_mm_molecule.py +++ b/openff/toolkit/_tests/test_mm_molecule.py @@ -137,7 +137,7 @@ def test_dict_roundtrip(self, water): for atom_index in range(roundtrip.n_atoms): assert ( roundtrip.atom(atom_index).atomic_number - == water.atom(atom_index).atomic_number # noqa + == water.atom(atom_index).atomic_number ) def test_dict_roundtrip_conformers(self, water): diff --git a/openff/toolkit/_tests/test_molecule.py b/openff/toolkit/_tests/test_molecule.py index a6a8b398a..199aba39a 100644 --- a/openff/toolkit/_tests/test_molecule.py +++ b/openff/toolkit/_tests/test_molecule.py @@ -102,7 +102,7 @@ def assert_molecule_is_equal(molecule1, molecule2, msg): def is_four_membered_ring_torsion(torsion): """Check that three atoms in the given torsion form a four-membered ring.""" # Push a copy of the first and second atom in the end to make the code simpler. - torsion = list(torsion) + [torsion[0], torsion[1]] + torsion = [*list(torsion), torsion[0], torsion[1]] is_four_membered_ring = True for i in range(4): @@ -154,9 +154,9 @@ def is_three_membered_ring_torsion(torsion): outside_atom_idx = atom_indices[0] # Check that the remaining two atoms are non-central atoms in the membered ring. - atom1, atom2 = [ + atom1, atom2 = ( i for i in torsion_atom_indices if i not in [central_atom_idx, outside_atom_idx] - ] + ) # The two atoms are bonded to each other. if atom2 not in bonds_by_atom_idx[atom1] or atom1 not in bonds_by_atom_idx[atom2]: return False @@ -177,10 +177,10 @@ def mini_drug_bank(xfail_mols=None, wip_mols=None): Parameters ---------- - xfail_mols : Dict[str, str or None] + xfail_mols : dict[str, str or None] Dictionary mapping the molecule names that are allowed to failed to the failure reason. - wip_mols : Dict[str, str or None] + wip_mols : dict[str, str or None] Dictionary mapping the molecule names that are work in progress to the failure reason. @@ -1028,7 +1028,7 @@ def test_create_from_file(self): filename = get_data_file_path("molecules/toluene.mol2") molecule1 = Molecule(filename, allow_undefined_stereo=True) - with open(filename, "r") as infile: + with open(filename) as infile: molecule2 = Molecule( infile, file_format="MOL2", allow_undefined_stereo=True ) @@ -1617,7 +1617,7 @@ def test_isomorphic_general(self): assert ( Molecule.are_isomorphic( ethanol, - [*topology.molecules][0], + next(iter(topology.molecules)), aromatic_matching=False, formal_charge_matching=False, bond_order_matching=False, @@ -1746,9 +1746,9 @@ def test_strip_atom_stereochemistry(self): """Test the basic behavior of strip_atom_stereochemistry""" mol = Molecule.from_smiles("CCC[N@@](C)CC") - nitrogen_idx = [ + nitrogen_idx = next( atom.molecule_atom_index for atom in mol.atoms if atom.symbol == "N" - ][0] + ) # TODO: This fails with RDKitToolkitWrapper because it perceives # the stereochemistry of this nitrogen as None @@ -3359,7 +3359,7 @@ def test_qcschema_round_trip(self): molecule_hash=entry.initial_molecule.identifiers.molecule_hash ) - qca_mol = [*iterator][0] + qca_mol = next(iter(iterator)) # mow make sure the majority of the qcschema attributes are the same # note we can not compare the full dict due to qcelemental differences @@ -4420,13 +4420,13 @@ def test_add_default_hierarchy_schemes(self): "insertion_code" in offmol.hierarchy_schemes["residues"].uniqueness_criteria ) assert "chain_id" in offmol.hierarchy_schemes["residues"].uniqueness_criteria - assert [*offmol.residues][0].residue_name == "ACE" - assert [*offmol.residues][0].residue_number == "1" - assert [*offmol.residues][0].insertion_code == " " - assert [*offmol.residues][0].chain_id == " " + assert next(iter(offmol.residues)).residue_name == "ACE" + assert next(iter(offmol.residues)).residue_number == "1" + assert next(iter(offmol.residues)).insertion_code == " " + assert next(iter(offmol.residues)).chain_id == " " assert "chain_id" in offmol.hierarchy_schemes["chains"].uniqueness_criteria - assert [*offmol.chains][0].chain_id == " " + assert next(iter(offmol.chains)).chain_id == " " def test_hierarchy_perceived_dipeptide(self): """Test populating and accessing HierarchyElements""" diff --git a/openff/toolkit/_tests/test_parameters.py b/openff/toolkit/_tests/test_parameters.py index e1800d482..bd9ae0836 100644 --- a/openff/toolkit/_tests/test_parameters.py +++ b/openff/toolkit/_tests/test_parameters.py @@ -1987,7 +1987,7 @@ def test_slice(self, opc): def test_smirks_getitem_forbidden(self, opc): """Test that SMIRKS/ParameterType can NOT be used to lookup virtual site types.""" - smirks = list(opc["VirtualSites"]._parameters)[0].smirks + smirks = next(iter(opc["VirtualSites"]._parameters)).smirks with pytest.raises( NotImplementedError, diff --git a/openff/toolkit/_tests/test_toolkits.py b/openff/toolkit/_tests/test_toolkits.py index 6991f6883..ace8fc715 100644 --- a/openff/toolkit/_tests/test_toolkits.py +++ b/openff/toolkit/_tests/test_toolkits.py @@ -1228,7 +1228,7 @@ def test_get_mol2_coordinates(self): ) # Test loading from file-like object - with open(filename, "r") as infile: + with open(filename) as infile: molecule2 = Molecule( infile, file_format="MOL2", toolkit_registry=toolkit_wrapper ) @@ -4585,7 +4585,7 @@ def deregister_from_global_registry(self): # Keep a copy of the original registry since this is a "global" variable accessible to other modules from copy import deepcopy - global_registry_copy = deepcopy(GLOBAL_TOOLKIT_REGISTRY) + global_registry_copy = deepcopy(GLOBAL_TOOLKIT_REGISTRY) # noqa: F823 first_toolkit = type(GLOBAL_TOOLKIT_REGISTRY.registered_toolkits[0]) num_toolkits = len(GLOBAL_TOOLKIT_REGISTRY.registered_toolkits) @@ -4595,7 +4595,7 @@ def deregister_from_global_registry(self): type(tk) for tk in GLOBAL_TOOLKIT_REGISTRY.registered_toolkits ] assert ( - len(GLOBAL_TOOLKIT_REGISTRY.registered_toolkits) == num_toolkits - 1 # noqa + len(GLOBAL_TOOLKIT_REGISTRY.registered_toolkits) == num_toolkits - 1 ) GLOBAL_TOOLKIT_REGISTRY = deepcopy(global_registry_copy) # noqa diff --git a/openff/toolkit/_tests/test_topology.py b/openff/toolkit/_tests/test_topology.py index 2dcd2f43d..657638a35 100644 --- a/openff/toolkit/_tests/test_topology.py +++ b/openff/toolkit/_tests/test_topology.py @@ -606,7 +606,7 @@ def test_from_openmm_virtual_sites(self, opc): with pytest.raises( VirtualSitesUnsupportedError, - match="Atom .* a virtual site", + match=r"Atom .* a virtual site", ): Topology.from_openmm( openmm_topology, @@ -781,7 +781,7 @@ def test_from_pdb(self): ligand = Molecule.from_file(get_data_file_path("molecules/PT2385.sdf")) stereoisomer1 = Molecule.from_smiles("[C@H](Cl)(F)/C=C/F") - stereoisomer2 = Molecule.from_smiles("[C@@H](Cl)(F)/C=C\F") + stereoisomer2 = Molecule.from_smiles(r"[C@@H](Cl)(F)/C=C\F") top = Topology.from_pdb( get_data_file_path("proteins/5tbm_complex_solv.pdb"), @@ -2027,7 +2027,7 @@ def test_roundtrip(self, oleic_acid, with_conformers, n_molecules, format): assert roundtrip.n_molecules == n_molecules assert roundtrip.n_atoms == oleic_acid.n_atoms * n_molecules - assert [*roundtrip.molecules][0].n_conformers == n_conformers + assert next(iter(roundtrip.molecules)).n_conformers == n_conformers @pytest.mark.parametrize( diff --git a/openff/toolkit/_tests/test_utils_callback.py b/openff/toolkit/_tests/test_utils_callback.py index 1d12f7057..05ca47024 100644 --- a/openff/toolkit/_tests/test_utils_callback.py +++ b/openff/toolkit/_tests/test_utils_callback.py @@ -29,7 +29,7 @@ def add_history_entry(cls, name, *args, **kwargs): if len(history_entry) == 0: cls.history.append(name) else: - cls.history.append([name] + history_entry) + cls.history.append([name, *history_entry]) def instance_callback(self, callbackable, event_name, *args, **kwargs): assert isinstance(self, object) diff --git a/openff/toolkit/_tests/test_utils_serialization.py b/openff/toolkit/_tests/test_utils_serialization.py index 574bf0e70..3a4b375f3 100644 --- a/openff/toolkit/_tests/test_utils_serialization.py +++ b/openff/toolkit/_tests/test_utils_serialization.py @@ -37,7 +37,7 @@ def write(filename, contents): elif type(contents) is bytes: mode = "wb" else: - raise Exception("Cannot handle contents of type {}".format(type(contents))) + raise Exception(f"Cannot handle contents of type {type(contents)}") with open(filename, mode) as outfile: outfile.write(contents) @@ -164,7 +164,7 @@ def setup_class(cls): from openff.toolkit.utils import get_data_file_path filename = get_data_file_path("test_forcefields/test_forcefield.offxml") - with open(filename, "r") as f: + with open(filename) as f: xml = f.read() dictionary = xmltodict.parse(xml) cls.thing = DictionaryContainer(dictionary) diff --git a/openff/toolkit/_tests/utils.py b/openff/toolkit/_tests/utils.py index 350ed135a..ac6b132ab 100644 --- a/openff/toolkit/_tests/utils.py +++ b/openff/toolkit/_tests/utils.py @@ -369,7 +369,7 @@ def quantities_allclose(quantity1, quantity2, **kwargs): if not quantity1.unit.is_compatible(quantity2.unit): raise ValueError( "The two quantities don't have compatible units: " - "{} and {}".format(quantity1.unit, quantity2.unit) + f"{quantity1.unit} and {quantity2.unit}" ) # Compare the values stripped of the units. quantity1 = quantity1.value_in_unit_system(openmm_unit.md_unit_system) @@ -762,9 +762,7 @@ def pretty_format_diff(self, other, new_line=True, indent=True): for par_name in sorted(diff.keys()): par1, par2 = diff[par_name] diff_list.append( - "{par_name}: {par1} != {par2}".format( - par_name=par_name, par1=par1, par2=par2 - ) + f"{par_name}: {par1} != {par2}" ) separator = "\n" if new_line else "" diff_str = separator.join(diff_list) @@ -792,7 +790,7 @@ def __str__(self): # Quantities in a dictionary are normally printed in the # format "Quantity(value, unit=unit)" so we make it prettier. par_str = ", ".join( - "{}: {}".format(p, self.parameters[p]) for p in parameter_order + f"{p}: {self.parameters[p]}" for p in parameter_order ) return "(" + par_str + ")" @@ -975,22 +973,16 @@ def _compare_parameters( # Print error. if len(different_parameters) > 0: diff_msg += ( - "\n\nThe following {interaction}s have different parameters " - "in the two {force_name}forces{systems_labels}:".format( - interaction=interaction_type, - force_name=force_name, - systems_labels=systems_labels, - ) + f"\n\nThe following {interaction_type}s have different parameters " + f"in the two {force_name}forces{systems_labels}:" ) for key in sorted(different_parameters.keys()): param1, param2 = different_parameters[key] parameters_diff = param1.pretty_format_diff(param2) - diff_msg += "\n{} {}:\n{}".format(interaction_type, key, parameters_diff) + diff_msg += f"\n{interaction_type} {key}:\n{parameters_diff}" if diff_msg != "": - diff_msg = ("A difference between {} was detected. " "Details follow.").format( - interaction_type - ) + diff_msg + diff_msg = (f"A difference between {interaction_type} was detected. " "Details follow.") + diff_msg raise FailedParameterComparisonError(diff_msg + "\n", different_parameters) @@ -1029,7 +1021,7 @@ def _get_force_parameters(force, system, ignored_parameters): """ raise NotImplementedError( - "Comparison between {}s is not currently " "supported.".format(type(force)) + f"Comparison between {type(force)}s is not currently " "supported." ) @@ -1785,9 +1777,9 @@ def evaluate_molecules_off(molecules, forcefield, minimize=False): def get_14_scaling_factors(omm_sys: openmm.System) -> tuple[list, list]: """Find the 1-4 scaling factors as they are applied to an OpenMM System""" - nonbond_force = [ + nonbond_force = next( f for f in omm_sys.getForces() if type(f) is openmm.NonbondedForce - ][0] + ) vdw_14 = list() coul_14 = list() diff --git a/openff/toolkit/_version.py b/openff/toolkit/_version.py index 355ac19a5..d50060413 100644 --- a/openff/toolkit/_version.py +++ b/openff/toolkit/_version.py @@ -173,7 +173,7 @@ def git_get_keywords(versionfile_abs: str) -> dict[str, str]: # _version.py. keywords: dict[str, str] = {} try: - with open(versionfile_abs, "r") as fobj: + with open(versionfile_abs) as fobj: for line in fobj: if line.strip().startswith("git_refnames ="): mo = re.search(r'=\s*"(.*)"', line) diff --git a/openff/toolkit/topology/_mm_molecule.py b/openff/toolkit/topology/_mm_molecule.py index 874722325..3fd267e7d 100644 --- a/openff/toolkit/topology/_mm_molecule.py +++ b/openff/toolkit/topology/_mm_molecule.py @@ -55,9 +55,7 @@ def add_bond(self, atom1, atom2, **kwargs): else: raise ValueError( "Invalid inputs to molecule._add_bond. Expected ints or Atoms. " - "Received {} (type {}) and {} (type {}) ".format( - atom1, type(atom1), atom2, type(atom2) - ) + f"Received {atom1} (type {type(atom1)}) and {atom2} (type {type(atom2)}) " ) bond = _SimpleBond(atom1_atom, atom2_atom, **kwargs) self.bonds.append(bond) @@ -346,7 +344,7 @@ def to_dict(self) -> dict: ) for conf in self._conformers: conf_unitless = conf.m_as(unit.angstrom) - conf_serialized, conf_shape = serialize_numpy((conf_unitless)) + conf_serialized, conf_shape = serialize_numpy(conf_unitless) molecule_dict["conformers"].append(conf_serialized) molecule_dict["hierarchy_schemes"] = dict() diff --git a/openff/toolkit/topology/molecule.py b/openff/toolkit/topology/molecule.py index da4009b7e..612540cb5 100644 --- a/openff/toolkit/topology/molecule.py +++ b/openff/toolkit/topology/molecule.py @@ -280,7 +280,7 @@ def __init__( self._molecule = molecule # From Jeff: I'm going to assume that this is implicit in the parent Molecule's ordering of atoms # self._molecule_atom_index = molecule_atom_index - self._bonds: list["Bond"] = list() + self._bonds: list[Bond] = list() if metadata is None: self._metadata = AtomMetadataDict() @@ -577,9 +577,7 @@ def __repr__(self): def __str__(self): # TODO: Also include which molecule this atom belongs to? - return "".format( - self._name, self._atomic_number - ) + return f"" # ============================================================================================= @@ -2448,7 +2446,7 @@ def dihedral(a): rotated = rotated + c # Update the coordinates - cooh_xyz[trans_indices_h] = rotated.reshape((-1)) + cooh_xyz[trans_indices_h] = rotated.reshape(-1) # Update conformers with rotated coordinates conformers[:, cooh_indices, :] = cooh_xyz @@ -3655,13 +3653,13 @@ def from_iupac( Create a molecule from an IUPAC name - >>> molecule = Molecule.from_iupac('4-[(4-methylpiperazin-1-yl)methyl]-N-(4-methyl-3-{[4-(pyridin-3-yl)pyrimidin-2-yl]amino}phenyl)benzamide') # noqa + >>> molecule = Molecule.from_iupac('4-[(4-methylpiperazin-1-yl)methyl]-N-(4-methyl-3-{[4-(pyridin-3-yl)pyrimidin-2-yl]amino}phenyl)benzamide') Create a molecule from a common name >>> molecule = Molecule.from_iupac('imatinib') - """ + """ # noqa: E501 if isinstance(toolkit_registry, ToolkitRegistry): molecule = toolkit_registry.call( "from_iupac", @@ -3752,7 +3750,7 @@ def from_topology(cls: type[FM], topology) -> FM: # TODO: Ensure we are dealing with an OpenFF Topology object if topology.n_molecules != 1: raise ValueError("Topology must contain exactly one molecule") - molecule = [i for i in topology.molecules][0] + molecule = next(iter(topology.molecules)) return cls(molecule) def to_topology(self): @@ -4024,7 +4022,7 @@ def from_polymer_pdb( "proteins/aa_residues_substructures_explicit_bond_orders_with_caps.json" ) - with open(substructure_file_path, "r") as subfile: + with open(substructure_file_path) as subfile: substructure_dictionary = json.load(subfile) offmol = toolkit_registry.call( @@ -4097,14 +4095,12 @@ def _to_xyz_file(self, file_path: Union[str, IO[str]]): if len(conformers) == 1: end: Union[str, int] = "" - title = ( - lambda frame: f'{self.name if self.name != "" else self.hill_formula}{frame}\n' - ) + def title(frame): + return f"{self.name if self.name != '' else self.hill_formula}{frame}\n" else: end = 1 - title = ( - lambda frame: f'{self.name if self.name != "" else self.hill_formula} Frame {frame}\n' - ) + def title(frame): + return f"{self.name if self.name != '' else self.hill_formula} Frame {frame}\n" # check if we have a file path or an open file object if isinstance(file_path, str): @@ -4985,7 +4981,7 @@ def remap( if any( not (isinstance(i, int) and 0 <= i < self.n_atoms) - for i in [*new_to_cur] + [*cur_to_new] + for i in [*new_to_cur, *cur_to_new] ): raise RemapIndexError( f"All indices in a mapping_dict for a molecule with {self.n_atoms}" @@ -5308,7 +5304,7 @@ def __init__(self, *args, **kwargs): ``other``? """ - super(Molecule, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) # TODO: Change this to add_atom(Atom) to improve encapsulation and extensibility? def add_atom( @@ -5640,7 +5636,7 @@ def perceive_residues( substructure_file_path = get_data_file_path( "proteins/aa_residues_substructures_with_caps.json" ) - with open(substructure_file_path, "r") as subfile: + with open(substructure_file_path) as subfile: substructure_dictionary = json.load(subfile) # TODO: Think of a better way to deal with no strict chirality case @@ -5890,7 +5886,7 @@ def __init__( ): raise TypeError( f"'uniqueness_criteria' kwarg must be a list or a tuple of strings," - f" received {repr(uniqueness_criteria)} " + f" received {uniqueness_criteria!r} " f"(type {type(uniqueness_criteria)}) instead." ) @@ -5898,13 +5894,13 @@ def __init__( if type(criterion) is not str: raise TypeError( f"Each item in the 'uniqueness_criteria' kwarg must be a string," - f" received {repr(criterion)} " + f" received {criterion!r} " f"(type {type(criterion)}) instead." ) if type(iterator_name) is not str: raise TypeError( - f"'iterator_name' kwarg must be a string, received {repr(iterator_name)} " + f"'iterator_name' kwarg must be a string, received {iterator_name!r} " f"(type {type(iterator_name)}) instead." ) self.parent = parent @@ -5945,7 +5941,7 @@ def perceive_hierarchy(self): self.hierarchy_elements = list() # Determine which atoms should get added to which HierarchyElements - hier_eles_to_add: defaultdict[tuple[Union[int, str]], list["Atom"]] = ( + hier_eles_to_add: defaultdict[tuple[Union[int, str]], list[Atom]] = ( defaultdict(list) ) for atom in self.parent.atoms: diff --git a/openff/toolkit/topology/topology.py b/openff/toolkit/topology/topology.py index ed6fa2a75..4ddd4edd6 100644 --- a/openff/toolkit/topology/topology.py +++ b/openff/toolkit/topology/topology.py @@ -703,8 +703,7 @@ def molecules(self) -> Generator[MoleculeLike, None, None]: # Yields instead of returning the list itself. This prevents user from modifying the list # outside the Topology's knowledge. This is essential to make sure that atom index caches # invalidate themselves during appropriate events. - for molecule in self._molecules: - yield molecule + yield from self._molecules def molecule(self, index: int) -> Union[Molecule, _SimpleMolecule]: """ @@ -739,8 +738,7 @@ def atoms(self) -> Generator["Atom", None, None]: atoms """ for molecule in self._molecules: - for atom in molecule.atoms: - yield atom + yield from molecule.atoms def atom_index(self, atom: "Atom") -> int: """ @@ -831,8 +829,7 @@ def bonds(self) -> Generator["Bond", None, None]: bonds """ for molecule in self.molecules: - for bond in molecule.bonds: - yield bond + yield from molecule.bonds @property def n_angles(self) -> int: @@ -843,8 +840,7 @@ def n_angles(self) -> int: def angles(self) -> Generator[tuple["Atom", ...], None, None]: """Iterator over the angles in this Topology. Returns a Generator of tuple[Atom].""" for molecule in self._molecules: - for angle in molecule.angles: - yield angle + yield from molecule.angles @property def n_propers(self) -> int: @@ -855,8 +851,7 @@ def n_propers(self) -> int: def propers(self) -> Generator[tuple[Union["Atom", _SimpleAtom], ...], None, None]: """Iterable of tuple[Atom]: iterator over the proper torsions in this Topology.""" for molecule in self.molecules: - for proper in molecule.propers: - yield proper + yield from molecule.propers @property def n_impropers(self) -> int: @@ -867,8 +862,7 @@ def n_impropers(self) -> int: def impropers(self) -> Generator[tuple["Atom", ...], None, None]: """Generator of tuple[Atom]: iterator over the possible improper torsions in this Topology.""" for molecule in self._molecules: - for improper in molecule.impropers: - yield improper + yield from molecule.impropers @property def smirnoff_impropers( @@ -908,8 +902,7 @@ def smirnoff_impropers( """ for molecule in self.molecules: - for smirnoff_improper in molecule.smirnoff_impropers: - yield smirnoff_improper + yield from molecule.smirnoff_impropers @property def amber_impropers( @@ -940,8 +933,7 @@ def amber_impropers( impropers, smirnoff_impropers """ for molecule in self.molecules: - for amber_improper in molecule.amber_impropers: - yield amber_improper + yield from molecule.amber_impropers def nth_degree_neighbors(self, n_degrees: int): """ @@ -968,8 +960,7 @@ def nth_degree_neighbors(self, n_degrees: int): passed. """ for molecule in self.molecules: - for pair in molecule.nth_degree_neighbors(n_degrees=n_degrees): - yield pair + yield from molecule.nth_degree_neighbors(n_degrees=n_degrees) class _ChemicalEnvironmentMatch: """Represents the match of a given chemical environment query, storing @@ -1419,7 +1410,7 @@ def from_openmm( # Convert all unique mols to graphs topology = cls() - graph_to_unq_mol: dict["Graph", FrozenMolecule] = {} + graph_to_unq_mol: dict[Graph, FrozenMolecule] = {} for unq_mol in unique_molecules: unq_mol_graph = unq_mol.to_networkx() for existing_graph in graph_to_unq_mol.keys(): @@ -1435,9 +1426,7 @@ def from_openmm( )[0]: msg = ( "Error: Two unique molecules have indistinguishable " - "graphs: {} and {}".format( - unq_mol, graph_to_unq_mol[existing_graph] - ) + f"graphs: {unq_mol} and {graph_to_unq_mol[existing_graph]}" ) raise DuplicateUniqueMoleculeError(msg) graph_to_unq_mol[unq_mol_graph] = unq_mol @@ -1742,7 +1731,7 @@ def from_pdb( "proteins/aa_residues_substructures_explicit_bond_orders_with_caps_explicit_connectivity.json" ) - with open(substructure_file_path, "r") as subfile: + with open(substructure_file_path) as subfile: substructure_dictionary = json.load( subfile ) # preserving order is useful later when saving metadata @@ -1782,7 +1771,7 @@ def from_pdb( smi = label_mol.to_smiles(mapped=True) # remove unmapped atoms from mapped smiles. This will catch things like # `[H]` and `[Cl]` but not anything with 3 characters like `[H:1]` - smi = re.sub("\[[A-Za-z]{1,2}\]", "[*]", smi) + smi = re.sub(r"\[[A-Za-z]{1,2}\]", "[*]", smi) # Remove any orphaned () that remain smi = smi.replace("()", "") @@ -2131,7 +2120,7 @@ def clear_positions(self): def set_positions(self, array: Quantity): """ - Set the positions in a topology by copying from a single n×3 array. + Set the positions in a topology by copying from a single (n, 3) array. Note that modifying the original array will not update the positions in the topology; it must be passed again to ``set_positions()``. @@ -2273,7 +2262,7 @@ def get_bond_between(self, i: Union[int, "Atom"], j: Union[int, "Atom"]) -> "Bon else: raise ValueError( "Invalid input passed to is_bonded(). Expected ints or `Atom`s, " - "got {} and {}".format(type(i), type(j)) + f"got {type(i)} and {type(j)}" ) for bond in atomi.bonds: @@ -2283,7 +2272,7 @@ def get_bond_between(self, i: Union[int, "Atom"], j: Union[int, "Atom"]) -> "Bon if atom == atomj: return bond - raise NotBondedError("No bond between atom {} and {}".format(i, j)) + raise NotBondedError(f"No bond between atom {i} and {j}") def is_bonded(self, i: Union[int, "Atom"], j: Union[int, "Atom"]) -> bool: """Returns True if the two atoms are bonded @@ -2599,8 +2588,7 @@ def hierarchy_iterator( """ for molecule in self._molecules: if hasattr(molecule, iter_name): - for item in getattr(molecule, iter_name): - yield item + yield from getattr(molecule, iter_name) def __getattr__(self, name: str) -> list["HierarchyElement"]: """If a requested attribute is not found, check the hierarchy schemes""" diff --git a/openff/toolkit/typing/engines/smirnoff/forcefield.py b/openff/toolkit/typing/engines/smirnoff/forcefield.py index 4bfb40529..731c1cbd1 100644 --- a/openff/toolkit/typing/engines/smirnoff/forcefield.py +++ b/openff/toolkit/typing/engines/smirnoff/forcefield.py @@ -98,7 +98,7 @@ def _get_installed_offxml_dir_paths() -> list[str]: def get_available_force_fields(full_paths=False): - """ + r""" Get the filenames of all available .offxml force field files. Availability is determined by what is discovered through the @@ -388,12 +388,9 @@ def _check_smirnoff_version_compatibility(self, version: str): parse(str(version)) < parse(str(self._MIN_SUPPORTED_SMIRNOFF_VERSION)) ): raise SMIRNOFFVersionError( - "SMIRNOFF offxml file was written with version {}, but this version of ForceField only supports " - "version {} to version {}".format( - version, - self._MIN_SUPPORTED_SMIRNOFF_VERSION, - self._MAX_SUPPORTED_SMIRNOFF_VERSION, - ) + f"SMIRNOFF offxml file was written with version {version}, but this version of " + f"ForceField only supports version {self._MIN_SUPPORTED_SMIRNOFF_VERSION} to " + f"version {self._MAX_SUPPORTED_SMIRNOFF_VERSION}" ) @property @@ -530,12 +527,9 @@ def _register_parameter_handler_classes( if tagname is not None: if tagname in self._parameter_handler_classes: raise ParameterHandlerRegistrationError( - "Attempting to register ParameterHandler {}, which provides a parser for tag" - " '{}', but ParameterHandler {} has already been registered to handle that tag.".format( - parameter_handler_class, - tagname, - self._parameter_handler_classes[tagname], - ) + f"Attempting to register ParameterHandler {parameter_handler_class}, which provides a parser " + "for tag '{tagname}', but ParameterHandler {self._parameter_handler_classes[tagname]} has " + "already been registered to handle that tag." ) self._parameter_handler_classes[tagname] = parameter_handler_class @@ -565,12 +559,10 @@ def _register_parameter_io_handler_classes( if serialization_format is not None: if serialization_format in self._parameter_io_handler_classes.keys(): raise ParameterHandlerRegistrationError( - "Attempting to register ParameterIOHandler {}, which provides a IO parser for format " - "'{}', but ParameterIOHandler {} has already been registered to handle that tag.".format( - parameter_io_handler_class, - serialization_format, - self._parameter_io_handler_classes[serialization_format], - ) + f"Attempting to register ParameterIOHandler {parameter_io_handler_class}, which provides a IO " + "parser for format '{serialization_format}', but ParameterIOHandler " + "{self._parameter_io_handler_classes[serialization_format]} has already been registered to " + "handle that tag." ) self._parameter_io_handler_classes[serialization_format] = ( parameter_io_handler_class @@ -596,10 +588,8 @@ def register_parameter_handler(self, parameter_handler: ParameterHandler): tagname = parameter_handler._TAGNAME if tagname in self._parameter_handlers.keys(): raise ParameterHandlerRegistrationError( - "Tried to register parameter handler '{}' for tag '{}', but " - "tag is already registered to {}".format( - parameter_handler, tagname, self._parameter_handlers[tagname] - ) + f"Tried to register parameter handler '{parameter_handler}' for tag '{tagname}', but " + f"tag is already registered to {self._parameter_handlers[tagname]}" ) self._parameter_handlers[parameter_handler._TAGNAME] = parameter_handler @@ -623,12 +613,8 @@ def register_parameter_io_handler(self, parameter_io_handler: ParameterIOHandler io_format = parameter_io_handler._FORMAT if io_format in self._parameter_io_handlers.keys(): raise ParameterHandlerRegistrationError( - "Tried to register parameter IO handler '{}' for tag '{}', but " - "tag is already registered to {}".format( - parameter_io_handler, - io_format, - self._parameter_io_handlers[io_format], - ) + f"Tried to register parameter IO handler '{parameter_io_handler}' for tag '{io_format}', but " + f"tag is already registered to {self._parameter_io_handlers[io_format]}" ) self._parameter_io_handlers[io_format] = parameter_io_handler @@ -842,11 +828,11 @@ def _to_smirnoff_data(self, discard_cosmetic_attributes: bool = False) -> dict: l1_dict["aromaticity_model"] = self._aromaticity_model # Write out author and date (if they have been set) - if not (self._author is None): + if self._author is not None: l1_dict["Author"] = self._author # Write out author and date (if they have been set) - if not (self._date is None): + if self._date is not None: l1_dict["Date"] = self._date for parameter_handler in self._parameter_handlers.values(): @@ -906,7 +892,7 @@ def _load_smirnoff_data( smirnoff_data = convert_0_2_smirnoff_to_0_3(smirnoff_data) # Ensure that SMIRNOFF is a top-level key of the dict - if not ("SMIRNOFF" in smirnoff_data): + if "SMIRNOFF" not in smirnoff_data: raise SMIRNOFFParseError( "'SMIRNOFF' must be a top-level key in the SMIRNOFF object model" ) @@ -1077,7 +1063,7 @@ def parse_smirnoff_from_source(self, source: Union[str, IO]) -> dict: f"{exception_type}\n{exception_msg}\n" ) - raise IOError(msg) + raise OSError(msg) def to_string( self, diff --git a/openff/toolkit/typing/engines/smirnoff/parameters.py b/openff/toolkit/typing/engines/smirnoff/parameters.py index f2116ef2c..90eafce28 100644 --- a/openff/toolkit/typing/engines/smirnoff/parameters.py +++ b/openff/toolkit/typing/engines/smirnoff/parameters.py @@ -586,7 +586,7 @@ def _convert_and_validate(self, instance, value): class IndexedMappedParameterAttribute(ParameterAttribute): - """The attribute of a parameter with an unspecified number of terms, where + r"""The attribute of a parameter with an unspecified number of terms, where each term is a mapping. Some parameters can be associated to multiple terms, @@ -1067,14 +1067,14 @@ def to_dict( for key, val in mapping.items(): attrib_name_indexed, attrib_name_mapped = attrib_name.split("_") smirnoff_dict[ - f"{attrib_name_indexed}{str(idx + 1)}_{attrib_name_mapped}{key}" + f"{attrib_name_indexed}{idx + 1!s}_{attrib_name_mapped}{key}" ] = val elif attrib_name in indexed_attribs: for idx, val in enumerate(attrib_value): smirnoff_dict[attrib_name + str(idx + 1)] = val elif attrib_name in mapped_attribs: for key, val in attrib_value.items(): - smirnoff_dict[f"{attrib_name}{str(key)}"] = val + smirnoff_dict[f"{attrib_name}{key!s}"] = val elif attrib_name == "version": smirnoff_dict[attrib_name] = str(attrib_value) else: @@ -1105,7 +1105,7 @@ def __getattr__(self, item): return indexed_mapped_attr_value[index][key] except (IndexError, KeyError) as err: raise MissingIndexedAttributeError( - f"{str(err)} '{item}' is out of bounds for indexed attribute '{attr_name}'" + f"{err!s} '{item}' is out of bounds for indexed attribute '{attr_name}'" ) # Otherwise, try indexed attribute @@ -1155,7 +1155,7 @@ def __setattr__(self, key, value): return except (IndexError, KeyError) as err: raise MissingIndexedAttributeError( - f"{str(err)} '{key}' is out of bounds for indexed attribute '{attr_name}'" + f"{err!s} '{key}' is out of bounds for indexed attribute '{attr_name}'" ) # Otherwise, try indexed attribute @@ -1648,7 +1648,7 @@ def __getitem__(self, val: Union[int, slice, str, "ParameterType"]): # type: ig # TODO: Rename to better reflect role as parameter base class? class ParameterType(_ParameterAttributeHandler): - """ + r""" Base class for SMIRNOFF parameter types. This base class provides utilities to create new parameter types. See @@ -2276,9 +2276,7 @@ def _find_matches( matches.update(matches_for_this_type) logger.debug( - "{:64} : {:8} matches".format( - parameter_type.smirks, len(matches_for_this_type) - ) + f"{parameter_type.smirks:64} : {len(matches_for_this_type):8} matches" ) logger.debug(f"{len(matches)} matches identified") @@ -2425,10 +2423,8 @@ def get_unitless_values(attr): ) if abs(this_val - other_val) > tolerance: raise IncompatibleParameterError( - "Difference between '{}' values is beyond allowed tolerance {}. " - "(handler value: {}, incompatible value: {}".format( - attr, tolerance, this_val, other_val - ) + f"Difference between '{attr}' values is beyond allowed tolerance {tolerance}. " + f"(handler value: {this_val}, incompatible value: {other_val}" ) def __getitem__(self, val): diff --git a/openff/toolkit/utils/__init__.py b/openff/toolkit/utils/__init__.py index ab74731c4..b23bf5222 100644 --- a/openff/toolkit/utils/__init__.py +++ b/openff/toolkit/utils/__init__.py @@ -6,6 +6,7 @@ DEFAULT_CHARGE_MODEL, DEFAULT_FRACTIONAL_BOND_ORDER_MODEL, ) +from openff.toolkit.utils.toolkit_registry import toolkit_registry_manager from openff.toolkit.utils.toolkits import ( AMBERTOOLS_AVAILABLE, BASIC_CHEMINFORMATICS_TOOLKITS, @@ -53,4 +54,3 @@ temporary_cd, unit_to_string, ) -from openff.toolkit.utils.toolkit_registry import toolkit_registry_manager diff --git a/openff/toolkit/utils/ambertools_wrapper.py b/openff/toolkit/utils/ambertools_wrapper.py index 1c1960c1c..353c00e32 100644 --- a/openff/toolkit/utils/ambertools_wrapper.py +++ b/openff/toolkit/utils/ambertools_wrapper.py @@ -258,12 +258,10 @@ def assign_partial_charges( # TODO: copy files into local directory to aid debugging? raise ChargeCalculationError( "Antechamber/sqm partial charge calculation failed on " - "molecule {} (SMILES {})".format( - molecule.name, molecule.to_smiles() - ) + f"molecule {molecule.name} (SMILES {molecule.to_smiles()})" ) # Read the charges - with open(f"{tmpdir}/charges.txt", "r") as infile: + with open(f"{tmpdir}/charges.txt") as infile: contents = infile.read() text_charges = contents.split() charges = np.zeros([molecule.n_atoms], np.float64) @@ -350,7 +348,7 @@ def _get_fractional_bond_orders_from_sqm_out( begin_sep = """ Bond Orders QMMM: NUM1 ELEM1 NUM2 ELEM2 BOND_ORDER -""" +""" # noqa end_sep = """ --------- Calculation Completed ---------- diff --git a/openff/toolkit/utils/base_wrapper.py b/openff/toolkit/utils/base_wrapper.py index 71b5bca45..eae936500 100644 --- a/openff/toolkit/utils/base_wrapper.py +++ b/openff/toolkit/utils/base_wrapper.py @@ -66,9 +66,7 @@ def decorator(func): @wraps(func) def wrapped_function(*args, **kwargs): if not cls.is_available(): - msg = "This function requires the {} toolkit".format( - cls._toolkit_name - ) + msg = f"This function requires the {cls._toolkit_name} toolkit" raise ToolkitUnavailableException(msg) value = func(*args, **kwargs) return value diff --git a/openff/toolkit/utils/exceptions.py b/openff/toolkit/utils/exceptions.py index 73a968699..cb841eaf7 100644 --- a/openff/toolkit/utils/exceptions.py +++ b/openff/toolkit/utils/exceptions.py @@ -426,8 +426,8 @@ def __init__( ): if omm_top is not None: self.omm_top = omm_top - self._atoms: list["OpenMMAtom"] = list(omm_top.atoms()) - self._bonds: list[tuple["OpenMMAtom", "OpenMMAtom"]] = list(omm_top.bonds()) + self._atoms: list[OpenMMAtom] = list(omm_top.atoms()) + self._bonds: list[tuple[OpenMMAtom, OpenMMAtom]] = list(omm_top.bonds()) if not (substructure_library): substructure_library = {} diff --git a/openff/toolkit/utils/openeye_wrapper.py b/openff/toolkit/utils/openeye_wrapper.py index 4d7ba3a09..fa441ad1f 100644 --- a/openff/toolkit/utils/openeye_wrapper.py +++ b/openff/toolkit/utils/openeye_wrapper.py @@ -22,7 +22,7 @@ from openff.toolkit import Quantity, unit if TYPE_CHECKING: - from openff.toolkit.topology.molecule import FrozenMolecule, Molecule, Bond, Atom + from openff.toolkit.topology.molecule import Atom, Bond, FrozenMolecule, Molecule from openff.units.elements import SYMBOLS @@ -313,7 +313,7 @@ def from_object( _cls=_cls, ) raise NotImplementedError( - "Cannot create Molecule from {} object".format(type(obj)) + f"Cannot create Molecule from {type(obj)} object" ) def _polymer_openmm_topology_to_offmol( @@ -665,7 +665,7 @@ def to_file( # Remove all but the first conformer when writing to SDF as we only support single conformer format if (file_format.lower() == "sdf") and oemol.NumConfs() > 1: - conf1 = [conf for conf in oemol.GetConfs()][0] + conf1 = next(iter(oemol.GetConfs())) flat_coords = list() for coord in conf1.GetCoords().values(): flat_coords.extend(coord) @@ -1234,25 +1234,22 @@ def from_openeye( if unspec_chiral or unspec_db: def oeatom_to_str(oeatom) -> str: - return "atomic num: {}, name: {}, idx: {}, aromatic: {}, chiral: {}".format( - oeatom.GetAtomicNum(), - oeatom.GetName(), - oeatom.GetIdx(), - oeatom.IsAromatic(), - oeatom.IsChiral(), + return ( + f"atomic num: {oeatom.GetAtomicNum()}, " + f"name: {oeatom.GetName()}, " + f"idx: {oeatom.GetIdx()}, " + f"aromatic: {oeatom.IsAromatic()}, " + f"chiral: {oeatom.IsChiral()}" ) + def oebond_to_str(oebond) -> str: - return "order: {}, chiral: {}".format( - oebond.GetOrder(), oebond.IsChiral() - ) + return f"order: {oebond.GetOrder()}, chiral: {oebond.IsChiral()}" def describe_oeatom(oeatom) -> str: - description = "Atom {} with bonds:".format(oeatom_to_str(oeatom)) + description = f"Atom {oeatom_to_str(oeatom)} with bonds:" for oebond in oeatom.GetBonds(): - description += "\nbond {} to atom {}".format( - oebond_to_str(oebond), oeatom_to_str(oebond.GetNbr(oeatom)) - ) + description += f"\nbond {oebond_to_str(oebond)} to atom {oeatom_to_str(oebond.GetNbr(oeatom))}" return description if ( @@ -1513,8 +1510,8 @@ def _connection_table_to_openeye( atom2_index = bond.molecule.atoms.index(bond.atom2) # Set arbitrary initial stereochemistry oeatom1, oeatom2 = oemol_atoms[atom1_index], oemol_atoms[atom2_index] - oeatom1_neighbor = [n for n in oeatom1.GetAtoms() if not n == oeatom2][0] - oeatom2_neighbor = [n for n in oeatom2.GetAtoms() if not n == oeatom1][0] + oeatom1_neighbor = next(n for n in oeatom1.GetAtoms() if not n == oeatom2) + oeatom2_neighbor = next(n for n in oeatom2.GetAtoms() if not n == oeatom1) # oebond.SetStereo([oeatom1, oeatom2], oechem.OEBondStereo_CisTrans, oechem.OEBondStereo_Cis) oebond.SetStereo( [oeatom1_neighbor, oeatom2_neighbor], @@ -1558,7 +1555,7 @@ def to_openeye( molecule: "FrozenMolecule", aromaticity_model: str = DEFAULT_AROMATICITY_MODEL, ): - """ + r""" Create an OpenEye molecule using the specified aromaticity model ``OEAtom`` s have a different set of allowed value for partial @@ -2226,7 +2223,7 @@ def generate_conformers( clear_existing: bool = True, make_carboxylic_acids_cis: bool = False, ): - """ + r""" Generate molecule conformers using OpenEye Omega. .. warning :: This API is experimental and subject to change. diff --git a/openff/toolkit/utils/rdkit_wrapper.py b/openff/toolkit/utils/rdkit_wrapper.py index ac4eb643f..8777a0931 100644 --- a/openff/toolkit/utils/rdkit_wrapper.py +++ b/openff/toolkit/utils/rdkit_wrapper.py @@ -86,13 +86,7 @@ class RDKitToolkitWrapper(base_wrapper.ToolkitWrapper): # TODO: Add TDT support _toolkit_file_read_formats = ["SDF", "MOL", "SMI"] - _toolkit_file_write_formats = [ - "SDF", - "MOL", - "SMI", - "PDB", - "TDT", - ] + _toolkit_file_write_formats = ["SDF", "MOL", "SMI", "PDB", "TDT"] def __init__(self): super().__init__() @@ -178,7 +172,7 @@ def from_object( allow_undefined_stereo=allow_undefined_stereo, ) raise NotImplementedError( - "Cannot create Molecule from {} object".format(type(obj)) + f"Cannot create Molecule from {type(obj)} object" ) def from_pdb_and_smiles( @@ -1668,7 +1662,7 @@ def generate_conformers( _cls=None, make_carboxylic_acids_cis: bool = False, ): - """ + r""" Generate molecule conformers using RDKit. .. warning :: This API is experimental and subject to change. @@ -2353,7 +2347,7 @@ def from_rdkit( else: raise UndefinedStereochemistryError( "In from_rdkit: Expected atom stereochemistry of R or S. " - "Got {} instead.".format(stereo_code) + f"Got {stereo_code} instead." ) res = rda.GetPDBResidueInfo() @@ -2427,9 +2421,7 @@ def from_rdkit( stereochemistry = "E" elif tag == Chem.BondStereo.STEREOTRANS or tag == Chem.BondStereo.STEREOCIS: raise ValueError( - "Expected RDKit bond stereochemistry of E or Z, got {} instead".format( - tag - ) + f"Expected RDKit bond stereochemistry of E or Z, got {tag} instead" ) offb._stereochemistry = stereochemistry fractional_bond_order = None @@ -2589,9 +2581,8 @@ def _connection_table_to_rdkit( # Something is wrong. err_msg = ( "Unknown atom stereochemistry encountered in to_rdkit. " - "Desired stereochemistry: {}. Set stereochemistry {}".format( - atom.stereochemistry, rdatom.GetProp("_CIPCode") - ) + f"Desired stereochemistry: {atom.stereochemistry}. " + f"Set stereochemistry {rdatom.GetProp('_CIPCode')}" ) raise RuntimeError(err_msg) @@ -2710,7 +2701,7 @@ def to_rdkit( bond.atom2.molecule_atom_index, ) rdbond = rdmol.GetBondBetweenAtoms(*atom_indices) - if not (bond.fractional_bond_order is None): + if bond.fractional_bond_order is not None: rdbond.SetDoubleProp( "fractional_bond_order", bond.fractional_bond_order ) @@ -2725,7 +2716,7 @@ def to_rdkit( rdmol.AddConformer(rdmol_conformer, assignId=True) # Retain charges, if present - if not (molecule._partial_charges is None): + if molecule._partial_charges is not None: rdk_indexed_charges = np.zeros(shape=molecule.n_atoms, dtype=float) for atom_idx, charge in enumerate(molecule._partial_charges): charge_unitless = charge.m_as(unit.elementary_charge) @@ -3223,10 +3214,7 @@ def _detect_undefined_stereo( if len(undefined_atom_indices) > 0: msg += "Undefined chiral centers are:\n" for undefined_atom_idx in undefined_atom_indices: - msg += " - Atom {symbol} (index {index})\n".format( - symbol=rdmol.GetAtomWithIdx(undefined_atom_idx).GetSymbol(), - index=undefined_atom_idx, - ) + msg += f" - Atom {rdmol.GetAtomWithIdx(undefined_atom_idx).GetSymbol()} (index {undefined_atom_idx})\n" # Details about undefined bond. if len(undefined_bond_indices) > 0: @@ -3234,12 +3222,9 @@ def _detect_undefined_stereo( for undefined_bond_idx in undefined_bond_indices: bond = rdmol.GetBondWithIdx(undefined_bond_idx) atom1, atom2 = bond.GetBeginAtom(), bond.GetEndAtom() - msg += " - Bond {bindex} (atoms {aindex1}-{aindex2} of element ({symbol1}-{symbol2})\n".format( - bindex=undefined_bond_idx, - aindex1=atom1.GetIdx(), - aindex2=atom2.GetIdx(), - symbol1=atom1.GetSymbol(), - symbol2=atom2.GetSymbol(), + msg += ( + f" - Bond {undefined_bond_idx} (atoms {atom1.GetIdx()}-{atom2.GetIdx()} of element " + "({atom1.GetSymbol()}-{atom2.GetSymbol()})\n" ) raise UndefinedStereochemistryError(err_msg_prefix + msg) diff --git a/openff/toolkit/utils/toolkit_registry.py b/openff/toolkit/utils/toolkit_registry.py index b6700f0d1..77bc1405d 100644 --- a/openff/toolkit/utils/toolkit_registry.py +++ b/openff/toolkit/utils/toolkit_registry.py @@ -273,7 +273,7 @@ def call( f'for args "{args}" and kwargs "{kwargs}"\n' ) - msg += "Available toolkits are: {}\n".format(self.registered_toolkits) + msg += f"Available toolkits are: {self.registered_toolkits}\n" # Append information about toolkits that implemented the method, but could not handle the provided parameters for toolkit, error in errors: msg += f" {toolkit} {type(error)} : {error}\n" diff --git a/openff/toolkit/utils/toolkits.py b/openff/toolkit/utils/toolkits.py index 641cd05fe..74f976412 100644 --- a/openff/toolkit/utils/toolkits.py +++ b/openff/toolkit/utils/toolkits.py @@ -209,8 +209,6 @@ msg += "Please install at least one of the following basic toolkits:\n" for wrapper in all_subclasses(ToolkitWrapper): if wrapper.toolkit_name is not None: - msg += "{} : {}\n".format( - wrapper._toolkit_name, wrapper._toolkit_installation_instructions - ) + msg += f"{wrapper._toolkit_name} : {wrapper._toolkit_installation_instructions}\n" # TODO: Make this a warning!? print(msg) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000..8667542a8 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,23 @@ +[tool.ruff] +line-length = 119 +exclude = ["examples/deprecated/", "utilities/deprecated"] + +[tool.ruff.lint] +ignore = ["E721","D105","D107","D200","D203","D212", "RUF012"] +select = ["I", "E", "F", "W", "NPY", "UP", "RUF"] + +[tool.ruff.lint.per-file-ignores] +"openff/toolkit/**/__init__.py" = ["F401"] +"openff/toolkit/_version.py" = ["UP", "RUF"] +"openff/toolkit/_tests/**/*.py" = ["E501"] +"openff/toolkit/typing/engines/smirnoff.parameters.py" = ["RUF012"] +"openff/toolkit/_tests/_stale_tests.py" = ["F821"] +"docs/users/molecule_cookbook.ipynb" = ["F821", "E402"] +"examples/visualization/visualization.ipynb" = ["F821"] + +[tool.ruff.lint.isort] +# can't find a clean way to get Rust's globset to handle this via regex ... +# https://docs.astral.sh/ruff/settings/#lint_isort_known-third-party +known-third-party = ["openff.interchange", "openff.utilities", "openff.units"] +known-first-party = ["openff.toolkit"] + diff --git a/setup.cfg b/setup.cfg index 1558e636a..4a831b3be 100644 --- a/setup.cfg +++ b/setup.cfg @@ -21,48 +21,6 @@ exclude_lines = if TYPE_CHECKING: @overload -[flake8] -max-line-length = 119 -ignore = E203,W605,W503,E704 -exclude = - openff/toolkit/_tests/_stale_tests.py -per-file-ignores = - openff/toolkit/*/__init__.py:F401 - openff/toolkit/utils/ambertools_wrapper.py:W293 - openff/toolkit/_tests/test_molecule.py - openff/toolkit/_tests/test_forcefield.py:E501 - openff/toolkit/_tests/test_toolkits.py:E501 - openff/toolkit/topology/molecule.py:E704 - -[isort] -profile=black -multi_line_output=3 -include_trailing_comma=True -force_grid_wrap=0 -use_parentheses=True -line_length=88 -known_third_party= - pkg_resources - packaging - IPython - pytest - numpy - networkx - rdkit - openeye - qcelemental - openmm - mdtraj - parmed - nglview - qcportal - bson - toml - yaml - msgpack - xmltodict -skip=openff/toolkit/utils/__init__.py - [versioneer] # Automatic version numbering scheme VCS = git diff --git a/setup.py b/setup.py index 5100d4282..5d1e83b00 100644 --- a/setup.py +++ b/setup.py @@ -2,15 +2,16 @@ openff-toolkit A modern, extensible library for molecular mechanics force field science from the Open Force Field Consortium. """ -from setuptools import setup, find_namespace_packages +from setuptools import find_namespace_packages, setup + import versioneer short_description = __doc__.split("\n") try: - with open("README.md", "r") as handle: + with open("README.md") as handle: long_description = handle.read() -except IOError: +except OSError: long_description = "\n".join(short_description[2:]), diff --git a/utilities/make_substructure_dict/_make_chemical_substructures.py b/utilities/make_substructure_dict/_make_chemical_substructures.py index 361b88cf3..6185cd9d5 100644 --- a/utilities/make_substructure_dict/_make_chemical_substructures.py +++ b/utilities/make_substructure_dict/_make_chemical_substructures.py @@ -1,4 +1,3 @@ -# noqa: INP001 import os from _cif_to_substructure_dict import CifSubstructures diff --git a/utilities/make_substructure_dict/_make_metadata_assignment_substructures.py b/utilities/make_substructure_dict/_make_metadata_assignment_substructures.py index fd1df0faa..e7f19be13 100644 --- a/utilities/make_substructure_dict/_make_metadata_assignment_substructures.py +++ b/utilities/make_substructure_dict/_make_metadata_assignment_substructures.py @@ -1,4 +1,3 @@ -# noqa: INP001 import os from _cif_to_substructure_dict import CifSubstructures diff --git a/utilities/test_plugins/custom_plugins/handler_plugins.py b/utilities/test_plugins/custom_plugins/handler_plugins.py index 23629ce37..8b7790613 100644 --- a/utilities/test_plugins/custom_plugins/handler_plugins.py +++ b/utilities/test_plugins/custom_plugins/handler_plugins.py @@ -1,4 +1,3 @@ -# noqa: INP001 from openff.toolkit import unit from openff.toolkit.typing.engines.smirnoff import ParameterHandler, ParameterIOHandler from openff.toolkit.typing.engines.smirnoff.parameters import ( diff --git a/utilities/test_plugins/setup.py b/utilities/test_plugins/setup.py index e6e1266c1..6f2ae13d9 100644 --- a/utilities/test_plugins/setup.py +++ b/utilities/test_plugins/setup.py @@ -1,4 +1,3 @@ -# noqa: INP001 """ openff-test-parameter-plugins A test package used to ensure that parameterhandler plugins are handled correctly diff --git a/versioneer.py b/versioneer.py index 1e3753e63..2abdfe820 100644 --- a/versioneer.py +++ b/versioneer.py @@ -310,15 +310,14 @@ import configparser import errno +import functools import json import os import re import subprocess import sys from pathlib import Path -from typing import Any, Callable, cast, Dict, List, Optional, Tuple, Union -from typing import NoReturn -import functools +from typing import Any, Callable, Dict, List, NoReturn, Optional, Tuple, Union, cast have_tomllib = True if sys.version_info >= (3, 11): @@ -384,8 +383,7 @@ def get_root() -> str: me_dir = os.path.normcase(os.path.splitext(my_path)[0]) vsr_dir = os.path.normcase(os.path.splitext(versioneer_py)[0]) if me_dir != vsr_dir and "VERSIONEER_PEP518" not in globals(): - print("Warning: build in %s is using versioneer.py from %s" - % (os.path.dirname(my_path), versioneer_py)) + print(f"Warning: build in {os.path.dirname(my_path)} is using versioneer.py from {versioneer_py}") except NameError: pass return root @@ -478,9 +476,9 @@ def run_command( for command in commands: try: - dispcmd = str([command] + args) + dispcmd = str([command, *args]) # remember shell=False, so use git.cmd on windows, not just git - process = subprocess.Popen([command] + args, cwd=cwd, env=env, + process = subprocess.Popen([command, *args], cwd=cwd, env=env, stdout=subprocess.PIPE, stderr=(subprocess.PIPE if hide_stderr else None), **popen_kwargs) @@ -489,18 +487,18 @@ def run_command( if e.errno == errno.ENOENT: continue if verbose: - print("unable to run %s" % dispcmd) + print(f"unable to run {dispcmd}") print(e) return None, None else: if verbose: - print("unable to find command, tried %s" % (commands,)) + print(f"unable to find command, tried {commands}") return None, None stdout = process.communicate()[0].strip().decode() if process.returncode != 0: if verbose: - print("unable to run %s (error)" % dispcmd) - print("stdout was %s" % stdout) + print(f"unable to run {dispcmd} (error)") + print(f"stdout was {stdout}") return None, process.returncode return stdout, process.returncode @@ -1200,7 +1198,7 @@ def git_get_keywords(versionfile_abs: str) -> Dict[str, str]: # _version.py. keywords: Dict[str, str] = {} try: - with open(versionfile_abs, "r") as fobj: + with open(versionfile_abs) as fobj: for line in fobj: if line.strip().startswith("git_refnames ="): mo = re.search(r'=\s*"(.*)"', line) @@ -1261,9 +1259,9 @@ def git_versions_from_keywords( # "stabilization", as well as "HEAD" and "master". tags = {r for r in refs if re.search(r'\d', r)} if verbose: - print("discarding '%s', no digits" % ",".join(refs - tags)) + print("discarding '{}', no digits".format(",".join(refs - tags))) if verbose: - print("likely tags: %s" % ",".join(sorted(tags))) + print("likely tags: {}".format(",".join(sorted(tags)))) for ref in sorted(tags): # sorting will prefer e.g. "2.0" over "2.0rc1" if ref.startswith(tag_prefix): @@ -1274,7 +1272,7 @@ def git_versions_from_keywords( if not re.match(r'\d', r): continue if verbose: - print("picking %s" % r) + print(f"picking {r}") return {"version": r, "full-revisionid": keywords["full"].strip(), "dirty": False, "error": None, @@ -1315,7 +1313,7 @@ def git_pieces_from_vcs( hide_stderr=not verbose) if rc != 0: if verbose: - print("Directory %s not under git control" % root) + print(f"Directory {root} not under git control") raise NotThisMethod("'git rev-parse --git-dir' returned error") # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] @@ -1388,8 +1386,7 @@ def git_pieces_from_vcs( mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) if not mo: # unparsable. Maybe git-describe is misbehaving? - pieces["error"] = ("unable to parse git-describe output: '%s'" - % describe_out) + pieces["error"] = (f"unable to parse git-describe output: '{describe_out}'") return pieces # tag @@ -1398,8 +1395,7 @@ def git_pieces_from_vcs( if verbose: fmt = "tag '%s' doesn't start with prefix '%s'" print(fmt % (full_tag, tag_prefix)) - pieces["error"] = ("tag '%s' doesn't start with prefix '%s'" - % (full_tag, tag_prefix)) + pieces["error"] = (f"tag '{full_tag}' doesn't start with prefix '{tag_prefix}'") return pieces pieces["closest-tag"] = full_tag[len(tag_prefix):] @@ -1448,7 +1444,7 @@ def do_vcs_install(versionfile_source: str, ipy: Optional[str]) -> None: files.append(versioneer_file) present = False try: - with open(".gitattributes", "r") as fobj: + with open(".gitattributes") as fobj: for line in fobj: if line.strip().startswith(versionfile_source): if "export-subst" in line.strip().split()[1:]: @@ -1460,7 +1456,7 @@ def do_vcs_install(versionfile_source: str, ipy: Optional[str]) -> None: with open(".gitattributes", "a+") as fobj: fobj.write(f"{versionfile_source} export-subst\n") files.append(".gitattributes") - run_command(GITS, ["add", "--"] + files) + run_command(GITS, ["add", "--", *files]) def versions_from_parentdir( @@ -1486,8 +1482,7 @@ def versions_from_parentdir( root = os.path.dirname(root) # up a level if verbose: - print("Tried directories %s but none started with prefix %s" % - (str(rootdirs), parentdir_prefix)) + print(f"Tried directories {rootdirs!s} but none started with prefix {parentdir_prefix}") raise NotThisMethod("rootdir doesn't start with parentdir_prefix") @@ -1533,7 +1528,7 @@ def write_to_version_file(filename: str, versions: Dict[str, Any]) -> None: with open(filename, "w") as f: f.write(SHORT_VERSION_PY % contents) - print("set %s to '%s'" % (filename, versions["version"])) + print("set {} to '{}'".format(filename, versions["version"])) def plus_or_dot(pieces: Dict[str, Any]) -> str: @@ -1649,13 +1644,13 @@ def render_pep440_post(pieces: Dict[str, Any]) -> str: if pieces["dirty"]: rendered += ".dev0" rendered += plus_or_dot(pieces) - rendered += "g%s" % pieces["short"] + rendered += "g{}".format(pieces["short"]) else: # exception #1 rendered = "0.post%d" % pieces["distance"] if pieces["dirty"]: rendered += ".dev0" - rendered += "+g%s" % pieces["short"] + rendered += "+g{}".format(pieces["short"]) return rendered @@ -1674,7 +1669,7 @@ def render_pep440_post_branch(pieces: Dict[str, Any]) -> str: if pieces["branch"] != "master": rendered += ".dev0" rendered += plus_or_dot(pieces) - rendered += "g%s" % pieces["short"] + rendered += "g{}".format(pieces["short"]) if pieces["dirty"]: rendered += ".dirty" else: @@ -1682,7 +1677,7 @@ def render_pep440_post_branch(pieces: Dict[str, Any]) -> str: rendered = "0.post%d" % pieces["distance"] if pieces["branch"] != "master": rendered += ".dev0" - rendered += "+g%s" % pieces["short"] + rendered += "+g{}".format(pieces["short"]) if pieces["dirty"]: rendered += ".dirty" return rendered @@ -1779,7 +1774,7 @@ def render(pieces: Dict[str, Any], style: str) -> Dict[str, Any]: elif style == "git-describe-long": rendered = render_git_describe_long(pieces) else: - raise ValueError("unknown style '%s'" % style) + raise ValueError(f"unknown style '{style}'") return {"version": rendered, "full-revisionid": pieces["long"], "dirty": pieces["dirty"], "error": None, @@ -1804,7 +1799,7 @@ def get_versions(verbose: bool = False) -> Dict[str, Any]: assert cfg.VCS is not None, "please set [versioneer]VCS= in setup.cfg" handlers = HANDLERS.get(cfg.VCS) - assert handlers, "unrecognized VCS '%s'" % cfg.VCS + assert handlers, f"unrecognized VCS '{cfg.VCS}'" verbose = verbose or bool(cfg.verbose) # `bool()` used to avoid `None` assert cfg.versionfile_source is not None, \ "please set versioneer.versionfile_source" @@ -1825,7 +1820,7 @@ def get_versions(verbose: bool = False) -> Dict[str, Any]: keywords = get_keywords_f(versionfile_abs) ver = from_keywords_f(keywords, cfg.tag_prefix, verbose) if verbose: - print("got version from expanded keyword %s" % ver) + print(f"got version from expanded keyword {ver}") return ver except NotThisMethod: pass @@ -1833,7 +1828,7 @@ def get_versions(verbose: bool = False) -> Dict[str, Any]: try: ver = versions_from_file(versionfile_abs) if verbose: - print("got version from file %s %s" % (versionfile_abs, ver)) + print(f"got version from file {versionfile_abs} {ver}") return ver except NotThisMethod: pass @@ -1844,7 +1839,7 @@ def get_versions(verbose: bool = False) -> Dict[str, Any]: pieces = from_vcs_f(cfg.tag_prefix, root, verbose) ver = render(pieces, cfg.style) if verbose: - print("got version from VCS %s" % ver) + print(f"got version from VCS {ver}") return ver except NotThisMethod: pass @@ -1853,7 +1848,7 @@ def get_versions(verbose: bool = False) -> Dict[str, Any]: if cfg.parentdir_prefix: ver = versions_from_parentdir(cfg.parentdir_prefix, root, verbose) if verbose: - print("got version from parentdir %s" % ver) + print(f"got version from parentdir {ver}") return ver except NotThisMethod: pass @@ -1910,12 +1905,12 @@ def finalize_options(self) -> None: def run(self) -> None: vers = get_versions(verbose=True) - print("Version: %s" % vers["version"]) - print(" full-revisionid: %s" % vers.get("full-revisionid")) - print(" dirty: %s" % vers.get("dirty")) - print(" date: %s" % vers.get("date")) + print("Version: {}".format(vers["version"])) + print(" full-revisionid: {}".format(vers.get("full-revisionid"))) + print(" dirty: {}".format(vers.get("dirty"))) + print(" date: {}".format(vers.get("date"))) if vers["error"]: - print(" error: %s" % vers["error"]) + print(" error: {}".format(vers["error"])) cmds["version"] = cmd_version # we override "build_py" in setuptools @@ -1957,7 +1952,7 @@ def run(self) -> None: if cfg.versionfile_build: target_versionfile = os.path.join(self.build_lib, cfg.versionfile_build) - print("UPDATING %s" % target_versionfile) + print(f"UPDATING {target_versionfile}") write_to_version_file(target_versionfile, versions) cmds["build_py"] = cmd_build_py @@ -1989,7 +1984,7 @@ def run(self) -> None: "version update. This can happen if you are running build_ext " "without first running build_py.") return - print("UPDATING %s" % target_versionfile) + print(f"UPDATING {target_versionfile}") write_to_version_file(target_versionfile, versions) cmds["build_ext"] = cmd_build_ext @@ -2008,7 +2003,7 @@ def run(self) -> None: cfg = get_config_from_root(root) versions = get_versions() target_versionfile = cfg.versionfile_source - print("UPDATING %s" % target_versionfile) + print(f"UPDATING {target_versionfile}") write_to_version_file(target_versionfile, versions) _build_exe.run(self) @@ -2037,7 +2032,7 @@ def run(self) -> None: cfg = get_config_from_root(root) versions = get_versions() target_versionfile = cfg.versionfile_source - print("UPDATING %s" % target_versionfile) + print(f"UPDATING {target_versionfile}") write_to_version_file(target_versionfile, versions) _py2exe.run(self) @@ -2113,7 +2108,7 @@ def make_release_tree(self, base_dir: str, files: List[str]) -> None: # (remembering that it may be a hardlink) and replace it with an # updated value target_versionfile = os.path.join(base_dir, cfg.versionfile_source) - print("UPDATING %s" % target_versionfile) + print(f"UPDATING {target_versionfile}") write_to_version_file(target_versionfile, self._versioneer_generated_versions) cmds["sdist"] = cmd_sdist @@ -2185,7 +2180,7 @@ def do_setup() -> int: print(CONFIG_ERROR, file=sys.stderr) return 1 - print(" creating %s" % cfg.versionfile_source) + print(f" creating {cfg.versionfile_source}") with open(cfg.versionfile_source, "w") as f: LONG = LONG_VERSION_PY[cfg.VCS] f.write(LONG % {"DOLLAR": "$", @@ -2200,24 +2195,24 @@ def do_setup() -> int: maybe_ipy: Optional[str] = ipy if os.path.exists(ipy): try: - with open(ipy, "r") as f: + with open(ipy) as f: old = f.read() except OSError: old = "" module = os.path.splitext(os.path.basename(cfg.versionfile_source))[0] snippet = INIT_PY_SNIPPET.format(module) if OLD_SNIPPET in old: - print(" replacing boilerplate in %s" % ipy) + print(f" replacing boilerplate in {ipy}") with open(ipy, "w") as f: f.write(old.replace(OLD_SNIPPET, snippet)) elif snippet not in old: - print(" appending to %s" % ipy) + print(f" appending to {ipy}") with open(ipy, "a") as f: f.write(snippet) else: - print(" %s unmodified" % ipy) + print(f" {ipy} unmodified") else: - print(" %s doesn't exist, ok" % ipy) + print(f" {ipy} doesn't exist, ok") maybe_ipy = None # Make VCS-specific changes. For git, this means creating/changing @@ -2232,7 +2227,7 @@ def scan_setup_py() -> int: found = set() setters = False errors = 0 - with open("setup.py", "r") as f: + with open("setup.py") as f: for line in f.readlines(): if "import versioneer" in line: found.add("import")