Add type annotations and mypy CI job

This commit is contained in:
Thomas Holder
2023-09-21 20:32:23 +02:00
parent ee89e36f58
commit 57d7c6721b
14 changed files with 233 additions and 149 deletions

View File

@@ -56,3 +56,13 @@ jobs:
with: with:
name: coverage-html name: coverage-html
path: htmlcov/* path: htmlcov/*
static_type_check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: "3.12"
- run: python -m pip install mypy types-setuptools
- run: mypy

2
.gitignore vendored
View File

@@ -13,3 +13,5 @@
docs/build docs/build
docs/source/api/*.rst docs/source/api/*.rst
build/ build/
dist/
.coverage

View File

@@ -7,9 +7,15 @@ The :class:`Atom` class contains all atom information found in the PDB file.
""" """
import string import string
from typing import cast, List, NoReturn, Optional, TYPE_CHECKING
from propka.lib import make_tidy_atom_label from propka.lib import make_tidy_atom_label
from . import hybrid36 from . import hybrid36
if TYPE_CHECKING:
from propka.group import Group
from propka.molecular_container import MolecularContainer
from propka.conformation_container import ConformationContainer
# Format strings that get used in multiple places (or are very complex) # Format strings that get used in multiple places (or are very complex)
PDB_LINE_FMT1 = ( PDB_LINE_FMT1 = (
@@ -37,37 +43,24 @@ class Atom:
removed as reading/writing PROPKA input is no longer supported. removed as reading/writing PROPKA input is no longer supported.
""" """
def __init__(self, line=None): def __init__(self, line: Optional[str] = None):
"""Initialize Atom object. """Initialize Atom object.
Args: Args:
line: Line from a PDB file to set properties of atom. line: Line from a PDB file to set properties of atom.
""" """
self.occ = None self.number_of_bonded_elements: NoReturn = cast(NoReturn, {}) # FIXME unused?
self.numb = None self.group: Optional[Group] = None
self.res_name = None self.group_type: Optional[str] = None
self.type = None self.cysteine_bridge: bool = False
self.chain_id = None self.bonded_atoms: List[Atom] = []
self.beta = None
self.icode = None
self.res_num = None
self.name = None
self.element = None
self.x = None
self.y = None
self.z = None
self.group = None
self.group_type = None
self.number_of_bonded_elements = {}
self.cysteine_bridge = False
self.bonded_atoms = []
self.residue = None self.residue = None
self.conformation_container = None self.conformation_container: Optional[ConformationContainer] = None
self.molecular_container = None self.molecular_container: Optional[MolecularContainer] = None
self.is_protonated = False self.is_protonated = False
self.steric_num_lone_pairs_set = False self.steric_num_lone_pairs_set = False
self.terminal = None self.terminal: Optional[str] = None
self.charge = 0 self.charge = 0.0
self.charge_set = False self.charge_set = False
self.steric_number = 0 self.steric_number = 0
self.number_of_lone_pairs = 0 self.number_of_lone_pairs = 0
@@ -84,7 +77,7 @@ class Atom:
self.sybyl_assigned = False self.sybyl_assigned = False
self.marvin_pka = False self.marvin_pka = False
def set_properties(self, line): def set_properties(self, line: Optional[str]):
"""Line from PDB file to set properties of atom. """Line from PDB file to set properties of atom.
Args: Args:
@@ -112,10 +105,8 @@ class Atom:
self.z = float(line[46:54].strip()) self.z = float(line[46:54].strip())
self.res_num = int(line[22:26].strip()) self.res_num = int(line[22:26].strip())
self.res_name = "{0:<3s}".format(line[17:20].strip()) self.res_name = "{0:<3s}".format(line[17:20].strip())
self.chain_id = line[21]
# Set chain id to "_" if it is just white space. # Set chain id to "_" if it is just white space.
if not self.chain_id.strip(): self.chain_id = line[21].strip() or '_'
self.chain_id = '_'
self.type = line[:6].strip().lower() self.type = line[:6].strip().lower()
# TODO - define nucleic acid residue names elsewhere # TODO - define nucleic acid residue names elsewhere
@@ -134,7 +125,7 @@ class Atom:
self.element = '{0:1s}{1:1s}'.format( self.element = '{0:1s}{1:1s}'.format(
self.element[0], self.element[1].lower()) self.element[0], self.element[1].lower())
def set_group_type(self, type_): def set_group_type(self, type_: str):
"""Set group type of atom. """Set group type of atom.
Args: Args:

View File

@@ -10,6 +10,10 @@ import math
import json import json
import pkg_resources import pkg_resources
import propka.calculations import propka.calculations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from propka.molecular_container import MolecularContainer
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -329,7 +333,7 @@ class BondMaker:
return True return True
return False return False
def find_bonds_for_molecules_using_boxes(self, molecules): def find_bonds_for_molecules_using_boxes(self, molecules: "MolecularContainer"):
""" Finds all bonds for a molecular container. """ Finds all bonds for a molecular container.
Args: Args:

View File

@@ -6,13 +6,17 @@ Mathematical helper functions.
""" """
import math import math
from typing import Iterable, Optional, Tuple, TypeVar
from .vector_algebra import _XYZ
_BoundXYZ_1 = TypeVar("_BoundXYZ_1", bound=_XYZ)
_BoundXYZ_2 = TypeVar("_BoundXYZ_2", bound=_XYZ)
#: Maximum distance used to bound calculations of smallest distance #: Maximum distance used to bound calculations of smallest distance
MAX_DISTANCE = 1e6 MAX_DISTANCE = 1e6
def squared_distance(atom1, atom2): def squared_distance(atom1: _XYZ, atom2: _XYZ) -> float:
"""Calculate the squared distance between two atoms. """Calculate the squared distance between two atoms.
Args: Args:
@@ -28,7 +32,7 @@ def squared_distance(atom1, atom2):
return res return res
def distance(atom1, atom2): def distance(atom1: _XYZ, atom2: _XYZ) -> float:
"""Calculate the distance between two atoms. """Calculate the distance between two atoms.
Args: Args:
@@ -40,7 +44,10 @@ def distance(atom1, atom2):
return math.sqrt(squared_distance(atom1, atom2)) return math.sqrt(squared_distance(atom1, atom2))
def get_smallest_distance(atoms1, atoms2): def get_smallest_distance(
atoms1: Iterable[_BoundXYZ_1],
atoms2: Iterable[_BoundXYZ_2],
) -> Tuple[Optional[_BoundXYZ_1], float, Optional[_BoundXYZ_2]]:
"""Calculate the smallest distance between two groups of atoms. """Calculate the smallest distance between two groups of atoms.
Args: Args:
@@ -59,4 +66,4 @@ def get_smallest_distance(atoms1, atoms2):
res_dist = dist res_dist = dist
res_atom1 = atom1 res_atom1 = atom1
res_atom2 = atom2 res_atom2 = atom2
return [res_atom1, math.sqrt(res_dist), res_atom2] return (res_atom1, math.sqrt(res_dist), res_atom2)

View File

@@ -6,6 +6,12 @@ Container data structure for molecular conformations.
""" """
import logging import logging
import functools import functools
from typing import Iterable, List, NoReturn, Optional, TYPE_CHECKING, Set
if TYPE_CHECKING:
from propka.atom import Atom
from propka.molecular_container import MolecularContainer
import propka.ligand import propka.ligand
from propka.output import make_interaction_map from propka.output import make_interaction_map
from propka.determinant import Determinant from propka.determinant import Determinant
@@ -13,7 +19,6 @@ from propka.coupled_groups import NCCG
from propka.determinants import set_backbone_determinants, set_ion_determinants from propka.determinants import set_backbone_determinants, set_ion_determinants
from propka.determinants import set_determinants from propka.determinants import set_determinants
from propka.group import Group, is_group from propka.group import Group, is_group
from typing import Iterable
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -38,7 +43,10 @@ class ConformationContainer:
PROPKA inputs is no longer supported. PROPKA inputs is no longer supported.
""" """
def __init__(self, name='', parameters=None, molecular_container=None): def __init__(self,
name: str = '',
parameters=None,
molecular_container: Optional["MolecularContainer"] = None):
"""Initialize conformation container. """Initialize conformation container.
Args: Args:
@@ -49,9 +57,9 @@ class ConformationContainer:
self.molecular_container = molecular_container self.molecular_container = molecular_container
self.name = name self.name = name
self.parameters = parameters self.parameters = parameters
self.atoms = [] self.atoms: List["Atom"] = []
self.groups = [] self.groups: List[Group] = []
self.chains = [] self.chains: List[str] = []
self.current_iter_item = 0 self.current_iter_item = 0
self.marvin_pkas_calculated = False self.marvin_pkas_calculated = False
self.non_covalently_coupled_groups = False self.non_covalently_coupled_groups = False
@@ -126,7 +134,8 @@ class ConformationContainer:
self.get_titratable_groups()))) > 0: self.get_titratable_groups()))) > 0:
self.non_covalently_coupled_groups = True self.non_covalently_coupled_groups = True
def find_bonded_titratable_groups(self, atom, num_bonds, original_atom): def find_bonded_titratable_groups(self, atom: "Atom", num_bonds: int,
original_atom: "Atom"):
"""Find bonded titrable groups. """Find bonded titrable groups.
Args: Args:
@@ -136,7 +145,7 @@ class ConformationContainer:
Returns: Returns:
a set of bonded atom groups a set of bonded atom groups
""" """
res = set() res: Set[Group] = set()
for bond_atom in atom.bonded_atoms: for bond_atom in atom.bonded_atoms:
# skip the original atom # skip the original atom
if bond_atom == original_atom: if bond_atom == original_atom:
@@ -152,7 +161,7 @@ class ConformationContainer:
bond_atom, num_bonds+1, original_atom) bond_atom, num_bonds+1, original_atom)
return res return res
def setup_and_add_group(self, group): def setup_and_add_group(self, group: Optional[Group]):
"""Check if we want to include this group in the calculations. """Check if we want to include this group in the calculations.
Args: Args:
@@ -166,7 +175,7 @@ class ConformationContainer:
self.init_group(group) self.init_group(group)
self.groups.append(group) self.groups.append(group)
def init_group(self, group): def init_group(self, group: Group):
"""Initialize the given Group object. """Initialize the given Group object.
Args: Args:
@@ -178,10 +187,11 @@ class ConformationContainer:
# If --titrate_only option is set, make non-specified residues # If --titrate_only option is set, make non-specified residues
# un-titratable: # un-titratable:
assert self.molecular_container is not None
titrate_only = self.molecular_container.options.titrate_only titrate_only = self.molecular_container.options.titrate_only
if titrate_only is not None: if titrate_only is not None:
atom = group.atom atom = group.atom
if not (atom.chain_id, atom.res_num, atom.icode) in titrate_only: if (atom.chain_id, atom.res_num, atom.icode) not in titrate_only:
group.titratable = False group.titratable = False
if group.residue_type == 'CYS': if group.residue_type == 'CYS':
group.exclude_cys_from_results = True group.exclude_cys_from_results = True
@@ -475,7 +485,7 @@ class ConformationContainer:
group for group in self.groups group for group in self.groups
if group.residue_type in self.parameters.ions.keys()] if group.residue_type in self.parameters.ions.keys()]
def get_group_names(self, group_list): def get_group_names(self, group_list: NoReturn) -> NoReturn: # FIXME unused?
"""Get names of groups in list. """Get names of groups in list.
Args: Args:
@@ -483,9 +493,11 @@ class ConformationContainer:
Returns: Returns:
list of groups list of groups
""" """
if TYPE_CHECKING:
assert False
return [group for group in self.groups if group.type in group_list] return [group for group in self.groups if group.type in group_list]
def get_ligand_atoms(self): def get_ligand_atoms(self) -> List["Atom"]:
"""Get atoms associated with ligands. """Get atoms associated with ligands.
Returns: Returns:
@@ -493,7 +505,7 @@ class ConformationContainer:
""" """
return [atom for atom in self.atoms if atom.type == 'hetatm'] return [atom for atom in self.atoms if atom.type == 'hetatm']
def get_heavy_ligand_atoms(self): def get_heavy_ligand_atoms(self) -> List["Atom"]:
"""Get heavy atoms associated with ligands. """Get heavy atoms associated with ligands.
Returns: Returns:
@@ -503,7 +515,7 @@ class ConformationContainer:
atom for atom in self.atoms atom for atom in self.atoms
if atom.type == 'hetatm' and atom.element != 'H'] if atom.type == 'hetatm' and atom.element != 'H']
def get_chain(self, chain): def get_chain(self, chain: str) -> List["Atom"]:
"""Get atoms associated with a specific chain. """Get atoms associated with a specific chain.
Args: Args:
@@ -513,7 +525,7 @@ class ConformationContainer:
""" """
return [atom for atom in self.atoms if atom.chain_id != chain] return [atom for atom in self.atoms if atom.chain_id != chain]
def add_atom(self, atom): def add_atom(self, atom: "Atom"):
"""Add atom to container. """Add atom to container.
Args: Args:
@@ -556,7 +568,7 @@ class ConformationContainer:
""" """
self.top_up_from_atoms(other.atoms) self.top_up_from_atoms(other.atoms)
def top_up_from_atoms(self, other_atoms: Iterable["propka.atom.Atom"]): def top_up_from_atoms(self, other_atoms: Iterable["Atom"]):
"""Adds atoms which are missing from this container. """Adds atoms which are missing from this container.
Args: Args:
@@ -613,7 +625,7 @@ class ConformationContainer:
self.atoms[i].numb = i+1 self.atoms[i].numb = i+1
@staticmethod @staticmethod
def sort_atoms_key(atom): def sort_atoms_key(atom: "Atom") -> float:
"""Generate key for atom sorting. """Generate key for atom sorting.
Args: Args:

View File

@@ -7,6 +7,12 @@ Energy calculations.
""" """
import math import math
import logging import logging
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from propka.conformation_container import ConformationContainer
from propka.group import Group
from propka.calculations import squared_distance, get_smallest_distance from propka.calculations import squared_distance, get_smallest_distance
@@ -27,13 +33,14 @@ COMBINED_NUM_BURIED_MAX = 900
SEPARATE_NUM_BURIED_MAX = 400 SEPARATE_NUM_BURIED_MAX = 400
def radial_volume_desolvation(parameters, group): def radial_volume_desolvation(parameters, group: "Group") -> None:
"""Calculate desolvation terms for group. """Calculate desolvation terms for group.
Args: Args:
parameters: parameters for desolvation calculation parameters: parameters for desolvation calculation
group: group of atoms for calculation group: group of atoms for calculation
""" """
assert group.atom.conformation_container is not None
all_atoms = group.atom.conformation_container.get_non_hydrogen_atoms() all_atoms = group.atom.conformation_container.get_non_hydrogen_atoms()
volume = 0.0 volume = 0.0
group.num_volume = 0 group.num_volume = 0
@@ -66,7 +73,7 @@ def radial_volume_desolvation(parameters, group):
* volume_after_allowance * scale_factor) * volume_after_allowance * scale_factor)
def calculate_scale_factor(parameters, weight): def calculate_scale_factor(parameters, weight: float) -> float:
"""Calculate desolvation scaling factor. """Calculate desolvation scaling factor.
Args: Args:
@@ -82,7 +89,7 @@ def calculate_scale_factor(parameters, weight):
return scale_factor return scale_factor
def calculate_weight(parameters, num_volume): def calculate_weight(parameters, num_volume: int) -> float:
"""Calculate the atom-based desolvation weight. """Calculate the atom-based desolvation weight.
TODO - figure out why a similar function exists in version.py TODO - figure out why a similar function exists in version.py
@@ -102,7 +109,7 @@ def calculate_weight(parameters, num_volume):
return weight return weight
def calculate_pair_weight(parameters, num_volume1, num_volume2): def calculate_pair_weight(parameters, num_volume1: int, num_volume2: int) -> float:
"""Calculate the atom-pair based desolvation weight. """Calculate the atom-pair based desolvation weight.
Args: Args:
@@ -120,7 +127,7 @@ def calculate_pair_weight(parameters, num_volume1, num_volume2):
return weight return weight
def hydrogen_bond_energy(dist, dpka_max, cutoffs, f_angle=1.0): def hydrogen_bond_energy(dist, dpka_max: float, cutoffs, f_angle=1.0) -> float:
"""Calculate hydrogen-bond interaction pKa shift. """Calculate hydrogen-bond interaction pKa shift.
Args: Args:
@@ -319,7 +326,7 @@ def check_coulomb_pair(parameters, group1, group2, dist):
return do_coulomb return do_coulomb
def coulomb_energy(dist, weight, parameters): def coulomb_energy(dist: float, weight: float, parameters) -> float:
"""Calculates the Coulomb interaction pKa shift based on Coulomb's law. """Calculates the Coulomb interaction pKa shift based on Coulomb's law.
Args: Args:
@@ -340,7 +347,7 @@ def coulomb_energy(dist, weight, parameters):
return abs(dpka) return abs(dpka)
def backbone_reorganization(_, conformation): def backbone_reorganization(_, conformation: "ConformationContainer") -> None:
"""Perform calculations related to backbone reorganizations. """Perform calculations related to backbone reorganizations.
NOTE - this was described in the code as "adding test stuff" NOTE - this was described in the code as "adding test stuff"

View File

@@ -11,8 +11,11 @@ Routines and classes for storing groups important to PROPKA calculations.
""" """
import logging import logging
import math import math
from typing import cast, Dict, Iterable, List, NoReturn, Optional
import propka.ligand import propka.ligand
import propka.protonate import propka.protonate
from propka.atom import Atom
from propka.ligand_pka_values import LigandPkaValues from propka.ligand_pka_values import LigandPkaValues
from propka.determinant import Determinant from propka.determinant import Determinant
@@ -57,7 +60,7 @@ class Group:
longer supported. longer supported.
""" """
def __init__(self, atom): def __init__(self, atom: Atom):
"""Initialize with an atom. """Initialize with an atom.
Args: Args:
@@ -67,7 +70,11 @@ class Group:
self.type = '' self.type = ''
atom.group = self atom.group = self
# set up data structures # set up data structures
self.determinants = {'sidechain': [], 'backbone': [], 'coulomb': []} self.determinants: Dict[str, List[Determinant]] = {
'sidechain': [],
'backbone': [],
'coulomb': [],
}
self.pka_value = 0.0 self.pka_value = 0.0
self.model_pka = 0.0 self.model_pka = 0.0
# Energy associated with volume interactions # Energy associated with volume interactions
@@ -84,16 +91,16 @@ class Group:
self.z = 0.0 self.z = 0.0
self.charge = 0 self.charge = 0
self.parameters = None self.parameters = None
self.exclude_cys_from_results = None self.exclude_cys_from_results = False
self.interaction_atoms_for_acids = [] self.interaction_atoms_for_acids: List[Atom] = []
self.interaction_atoms_for_bases = [] self.interaction_atoms_for_bases: List[Atom] = []
self.model_pka_set = False self.model_pka_set = False
self.intrinsic_pka = None self.intrinsic_pka = None
self.titratable = None self.titratable = False
# information on covalent and non-covalent coupling # information on covalent and non-covalent coupling
self.non_covalently_coupled_groups = [] self.non_covalently_coupled_groups: List["Group"] = []
self.covalently_coupled_groups = [] self.covalently_coupled_groups: List["Group"] = []
self.coupled_titrating_group = None self.coupled_titrating_group: Optional["Group"] = None
self.common_charge_centre = False self.common_charge_centre = False
self.residue_type = self.atom.res_name self.residue_type = self.atom.res_name
if self.atom.terminal: if self.atom.terminal:
@@ -112,9 +119,9 @@ class Group:
self.label = fmt.format( self.label = fmt.format(
type=self.residue_type, name=atom.name, chain=atom.chain_id) type=self.residue_type, name=atom.name, chain=atom.chain_id)
# container for squared distances # container for squared distances
self.squared_distances = {} self.squared_distances: NoReturn = cast(NoReturn, {}) # FIXME unused?
def couple_covalently(self, other): def couple_covalently(self, other: "Group") -> None:
"""Couple this group with another group. """Couple this group with another group.
Args: Args:
@@ -126,7 +133,7 @@ class Group:
if self not in other.covalently_coupled_groups: if self not in other.covalently_coupled_groups:
other.covalently_coupled_groups.append(self) other.covalently_coupled_groups.append(self)
def couple_non_covalently(self, other): def couple_non_covalently(self, other: "Group") -> None:
"""Non-covalenthly couple this group with another group. """Non-covalenthly couple this group with another group.
Args: Args:
@@ -154,7 +161,7 @@ class Group:
""" """
return self.non_covalently_coupled_groups return self.non_covalently_coupled_groups
def share_determinants(self, others): def share_determinants(self, others: Iterable["Group"]) -> None:
"""Share determinants between this group and others. """Share determinants between this group and others.
Args: Args:
@@ -172,7 +179,7 @@ class Group:
self.calculate_total_pka() self.calculate_total_pka()
the_other.calculate_total_pka() the_other.calculate_total_pka()
def share_determinant(self, new_determinant, type_): def share_determinant(self, new_determinant: Determinant, type_: str) -> None:
"""Add determinant to this group's list of determinants. """Add determinant to this group's list of determinants.
Args: Args:
@@ -230,7 +237,7 @@ class Group:
self.add_determinant(determinant, type_) self.add_determinant(determinant, type_)
return self return self
def add_determinant(self, new_determinant, type_): def add_determinant(self, new_determinant: Determinant, type_: str) -> None:
"""Add to current and creates non-present determinants. """Add to current and creates non-present determinants.
Args: Args:
@@ -247,7 +254,7 @@ class Group:
self.determinants[type_].append(Determinant(new_determinant.group, self.determinants[type_].append(Determinant(new_determinant.group,
new_determinant.value)) new_determinant.value))
def set_determinant(self, new_determinant, type_): def set_determinant(self, new_determinant: Determinant, type_: str) -> None:
"""Overwrite current and create non-present determinants. """Overwrite current and create non-present determinants.
Args: Args:
@@ -345,8 +352,8 @@ class Group:
# set the main atom as interaction atom # set the main atom as interaction atom
self.set_interaction_atoms([self.atom], [self.atom]) self.set_interaction_atoms([self.atom], [self.atom])
def set_interaction_atoms(self, interaction_atoms_for_acids, def set_interaction_atoms(self, interaction_atoms_for_acids: List[Atom],
interaction_atoms_for_bases): interaction_atoms_for_bases: List[Atom]):
"""Set interacting atoms and group types. """Set interacting atoms and group types.
Args: Args:
@@ -359,10 +366,10 @@ class Group:
self.interaction_atoms_for_bases = interaction_atoms_for_bases self.interaction_atoms_for_bases = interaction_atoms_for_bases
# check if all atoms have been identified # check if all atoms have been identified
ok = True ok = True
for [expect, found, _] in [[EXPECTED_ATOMS_ACID_INTERACTIONS, for (expect, found) in [
self.interaction_atoms_for_acids, 'acid'], (EXPECTED_ATOMS_ACID_INTERACTIONS, self.interaction_atoms_for_acids),
[EXPECTED_ATOMS_BASE_INTERACTIONS, (EXPECTED_ATOMS_BASE_INTERACTIONS, self.interaction_atoms_for_bases),
self.interaction_atoms_for_bases, 'base']]: ]:
if self.type in expect.keys(): if self.type in expect.keys():
for elem in expect[self.type].keys(): for elem in expect[self.type].keys():
if (len([a for a in found if a.element == elem]) if (len([a for a in found if a.element == elem])
@@ -395,7 +402,7 @@ class Group:
' {0:s}'.format( ' {0:s}'.format(
str(self.interaction_atoms_for_bases[i]))) str(self.interaction_atoms_for_bases[i])))
def get_interaction_atoms(self, interacting_group): def get_interaction_atoms(self, interacting_group) -> List[Atom]:
"""Get atoms involved in interaction with other group. """Get atoms involved in interaction with other group.
Args: Args:
@@ -403,6 +410,7 @@ class Group:
Returns: Returns:
list of atoms list of atoms
""" """
assert self.parameters is not None
if interacting_group.residue_type in self.parameters.base_list: if interacting_group.residue_type in self.parameters.base_list:
return self.interaction_atoms_for_bases return self.interaction_atoms_for_bases
else: else:
@@ -518,7 +526,7 @@ class Group:
self.model_pka + self.energy_volume + self.energy_local self.model_pka + self.energy_volume + self.energy_local
+ back_bone + side_chain) + back_bone + side_chain)
def get_summary_string(self, remove_penalised_group=False): def get_summary_string(self, remove_penalised_group: bool = False) -> str:
"""Create summary string for this group. """Create summary string for this group.
Args: Args:
@@ -1210,7 +1218,7 @@ class TitratableLigandGroup(Group):
self.model_pka_set = True self.model_pka_set = True
def is_group(parameters, atom): def is_group(parameters, atom: Atom) -> Optional[Group]:
"""Identify whether the atom belongs to a group. """Identify whether the atom belongs to a group.
Args: Args:
@@ -1244,7 +1252,7 @@ def is_group(parameters, atom):
return None return None
def is_protein_group(parameters, atom): def is_protein_group(parameters, atom: Atom) -> Optional[Group]:
"""Identify whether the atom belongs to a protein group. """Identify whether the atom belongs to a protein group.
Args: Args:
@@ -1278,7 +1286,7 @@ def is_protein_group(parameters, atom):
return None return None
def is_ligand_group_by_groups(_, atom): def is_ligand_group_by_groups(_, atom: Atom) -> Optional[Group]:
"""Identify whether the atom belongs to a ligand group by checking groups. """Identify whether the atom belongs to a ligand group by checking groups.
Args: Args:
@@ -1360,7 +1368,7 @@ def is_ligand_group_by_groups(_, atom):
return None return None
def is_ligand_group_by_marvin_pkas(parameters, atom): def is_ligand_group_by_marvin_pkas(parameters, atom: Atom) -> Optional[Group]:
"""Identify whether the atom belongs to a ligand group by calculating """Identify whether the atom belongs to a ligand group by calculating
'Marvin pKas'. 'Marvin pKas'.
@@ -1375,6 +1383,7 @@ def is_ligand_group_by_marvin_pkas(parameters, atom):
# calculate Marvin ligand pkas for this conformation container # calculate Marvin ligand pkas for this conformation container
# if not already done # if not already done
# TODO - double-check testing coverage of these functions. # TODO - double-check testing coverage of these functions.
assert atom.conformation_container is not None
if not atom.conformation_container.marvin_pkas_calculated: if not atom.conformation_container.marvin_pkas_calculated:
lpka = LigandPkaValues(parameters) lpka = LigandPkaValues(parameters)
lpka.get_marvin_pkas_for_molecular_container( lpka.get_marvin_pkas_for_molecular_container(
@@ -1396,7 +1405,7 @@ def is_ligand_group_by_marvin_pkas(parameters, atom):
return None return None
def is_ion_group(parameters, atom): def is_ion_group(parameters, atom: Atom) -> Optional[Group]:
"""Identify whether the atom belongs to an ion group. """Identify whether the atom belongs to an ion group.
Args: Args:

View File

@@ -7,15 +7,19 @@ Calculations related to hydrogen placement.
""" """
import math import math
import logging import logging
from typing import List, Optional, Tuple, TYPE_CHECKING
from propka.protonate import Protonate from propka.protonate import Protonate
from propka.bonds import BondMaker from propka.bonds import BondMaker
from propka.atom import Atom from propka.atom import Atom
if TYPE_CHECKING:
from propka.molecular_container import MolecularContainer
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
def setup_bonding_and_protonation(molecular_container): def setup_bonding_and_protonation(molecular_container: "MolecularContainer") -> None:
"""Set up bonding and protonation for a molecule. """Set up bonding and protonation for a molecule.
Args: Args:
@@ -34,7 +38,7 @@ def setup_bonding_and_protonation(molecular_container):
protonator.protonate(molecular_container) protonator.protonate(molecular_container)
def setup_bonding(molecular_container): def setup_bonding(molecular_container: "MolecularContainer") -> BondMaker:
"""Set up bonding for a molecular container. """Set up bonding for a molecular container.
Args: Args:
@@ -47,7 +51,7 @@ def setup_bonding(molecular_container):
return my_bond_maker return my_bond_maker
def setup_bonding_and_protonation_30_style(molecular_container): def setup_bonding_and_protonation_30_style(molecular_container: "MolecularContainer") -> BondMaker:
"""Set up bonding for a molecular container. """Set up bonding for a molecular container.
Args: Args:
@@ -63,7 +67,7 @@ def setup_bonding_and_protonation_30_style(molecular_container):
return bond_maker return bond_maker
def protonate_30_style(molecular_container): def protonate_30_style(molecular_container: "MolecularContainer") -> None:
"""Protonate the molecule. """Protonate the molecule.
Args: Args:
@@ -73,9 +77,9 @@ def protonate_30_style(molecular_container):
_LOGGER.info('Now protonating %s', name) _LOGGER.info('Now protonating %s', name)
# split atom into residues # split atom into residues
curres = -1000000 curres = -1000000
residue = [] residue: List[Atom] = []
o_atom = None o_atom: Optional[Atom] = None
c_atom = None c_atom: Optional[Atom] = None
for atom in molecular_container.conformations[name].atoms: for atom in molecular_container.conformations[name].atoms:
if atom.res_num != curres: if atom.res_num != curres:
curres = atom.res_num curres = atom.res_num
@@ -100,7 +104,7 @@ def protonate_30_style(molecular_container):
residue.append(atom) residue.append(atom)
def set_ligand_atom_names(molecular_container): def set_ligand_atom_names(molecular_container: "MolecularContainer") -> None:
"""Set names for ligands in molecular container. """Set names for ligands in molecular container.
Args: Args:
@@ -110,7 +114,7 @@ def set_ligand_atom_names(molecular_container):
molecular_container.conformations[name].set_ligand_atom_names() molecular_container.conformations[name].set_ligand_atom_names()
def add_arg_hydrogen(residue): def add_arg_hydrogen(residue: List[Atom]) -> List[Atom]:
"""Adds Arg hydrogen atoms to residues according to the 'old way'. """Adds Arg hydrogen atoms to residues according to the 'old way'.
Args: Args:
@@ -142,7 +146,7 @@ def add_arg_hydrogen(residue):
return [h1_atom, h2_atom, h3_atom, h4_atom, h5_atom] return [h1_atom, h2_atom, h3_atom, h4_atom, h5_atom]
def add_his_hydrogen(residue): def add_his_hydrogen(residue: List[Atom]) -> None:
"""Adds His hydrogen atoms to residues according to the 'old way'. """Adds His hydrogen atoms to residues according to the 'old way'.
Args: Args:
@@ -165,7 +169,7 @@ def add_his_hydrogen(residue):
he_atom.name = "HNE" he_atom.name = "HNE"
def add_trp_hydrogen(residue): def add_trp_hydrogen(residue: List[Atom]) -> None:
"""Adds Trp hydrogen atoms to residues according to the 'old way'. """Adds Trp hydrogen atoms to residues according to the 'old way'.
Args: Args:
@@ -188,7 +192,7 @@ def add_trp_hydrogen(residue):
he_atom.name = "HNE" he_atom.name = "HNE"
def add_amd_hydrogen(residue): def add_amd_hydrogen(residue: List[Atom]) -> None:
"""Adds Gln & Asn hydrogen atoms to residues according to the 'old way'. """Adds Gln & Asn hydrogen atoms to residues according to the 'old way'.
Args: Args:
@@ -217,7 +221,9 @@ def add_amd_hydrogen(residue):
h2_atom.name = "HN2" h2_atom.name = "HN2"
def add_backbone_hydrogen(residue, o_atom, c_atom): def add_backbone_hydrogen(residue: List[Atom],
o_atom: Optional[Atom],
c_atom: Optional[Atom]) -> Tuple[Optional[Atom], Optional[Atom]]:
"""Adds hydrogen backbone atoms to residues according to the old way. """Adds hydrogen backbone atoms to residues according to the old way.
dR is wrong for the N-terminus (i.e. first residue) but it doesn't affect dR is wrong for the N-terminus (i.e. first residue) but it doesn't affect
@@ -240,18 +246,18 @@ def add_backbone_hydrogen(residue, o_atom, c_atom):
new_c_atom = atom new_c_atom = atom
if atom.name == "O": if atom.name == "O":
new_o_atom = atom new_o_atom = atom
if None in [c_atom, o_atom, n_atom]: if c_atom is None or o_atom is None or n_atom is None:
return [new_o_atom, new_c_atom] return (new_o_atom, new_c_atom)
if n_atom.res_name == "PRO": if n_atom.res_name == "PRO":
# PRO doesn't have an H-atom; do nothing # PRO doesn't have an H-atom; do nothing
pass pass
else: else:
h_atom = protonate_direction(n_atom, o_atom, c_atom) h_atom = protonate_direction(n_atom, o_atom, c_atom)
h_atom.name = "H" h_atom.name = "H"
return [new_o_atom, new_c_atom] return (new_o_atom, new_c_atom)
def protonate_direction(x1_atom, x2_atom, x3_atom): def protonate_direction(x1_atom: Atom, x2_atom: Atom, x3_atom: Atom) -> Atom:
"""Protonates an atom, x1_atom, given a direction. """Protonates an atom, x1_atom, given a direction.
New direction for x1_atom proton is (x2_atom -> x3_atom). New direction for x1_atom proton is (x2_atom -> x3_atom).
@@ -275,7 +281,7 @@ def protonate_direction(x1_atom, x2_atom, x3_atom):
return h_atom return h_atom
def protonate_average_direction(x1_atom, x2_atom, x3_atom): def protonate_average_direction(x1_atom: Atom, x2_atom: Atom, x3_atom: Atom) -> Atom:
"""Protonates an atom, x1_atom, given a direction. """Protonates an atom, x1_atom, given a direction.
New direction for x1_atom is (x1_atom/x2_atom -> x3_atom). New direction for x1_atom is (x1_atom/x2_atom -> x3_atom).
@@ -301,7 +307,7 @@ def protonate_average_direction(x1_atom, x2_atom, x3_atom):
return h_atom return h_atom
def protonate_sp2(x1_atom, x2_atom, x3_atom): def protonate_sp2(x1_atom: Atom, x2_atom: Atom, x3_atom: Atom) -> Atom:
"""Protonates a SP2 atom, given a list of atoms """Protonates a SP2 atom, given a list of atoms
Args: Args:
@@ -323,7 +329,7 @@ def protonate_sp2(x1_atom, x2_atom, x3_atom):
return h_atom return h_atom
def make_new_h(atom, x, y, z): def make_new_h(atom: Atom, x: float, y: float, z: float) -> Atom:
"""Add a new hydrogen to an atom at the specified position. """Add a new hydrogen to an atom at the specified position.
Args: Args:
@@ -347,5 +353,6 @@ def make_new_h(atom, x, y, z):
new_h.number_of_protons_to_add = 0 new_h.number_of_protons_to_add = 0
new_h.num_pi_elec_2_3_bonds = 0 new_h.num_pi_elec_2_3_bonds = 0
atom.bonded_atoms.append(new_h) atom.bonded_atoms.append(new_h)
assert atom.conformation_container is not None
atom.conformation_container.add_atom(new_h) atom.conformation_container.add_atom(new_h)
return new_h return new_h

View File

@@ -10,12 +10,15 @@ Input routines.
:func:`get_atom_lines_from_input`) have been removed. :func:`get_atom_lines_from_input`) have been removed.
""" """
import typing import typing
from typing import Iterator, Tuple
import contextlib import contextlib
from pathlib import Path from pathlib import Path
from pkg_resources import resource_filename from pkg_resources import resource_filename
from propka.lib import protein_precheck from propka.lib import protein_precheck
from propka.atom import Atom from propka.atom import Atom
from propka.conformation_container import ConformationContainer from propka.conformation_container import ConformationContainer
from propka.molecular_container import MolecularContainer
from propka.parameters import Parameters
def open_file_for_reading( def open_file_for_reading(
@@ -27,18 +30,14 @@ def open_file_for_reading(
input_file: path to file or file-like object. If file-like object, input_file: path to file or file-like object. If file-like object,
then will attempt seek(0). then will attempt seek(0).
""" """
try: if not isinstance(input_file, (str, Path)):
input_file.seek(0) input_file.seek(0)
except AttributeError: return contextlib.nullcontext(input_file)
pass
else:
# TODO use contextlib.nullcontext when dropping Python 3.6 support
return contextlib.contextmanager(lambda: (yield input_file))()
return contextlib.closing(open(input_file, 'rt')) return contextlib.closing(open(input_file, 'rt'))
def read_molecule_file(filename: str, mol_container, stream=None): def read_molecule_file(filename: str, mol_container: MolecularContainer, stream=None) -> MolecularContainer:
"""Read input file or stream (PDB or PROPKA) for a molecular container """Read input file or stream (PDB or PROPKA) for a molecular container
Args: Args:
@@ -123,7 +122,7 @@ def read_molecule_file(filename: str, mol_container, stream=None):
return mol_container return mol_container
def read_parameter_file(input_file, parameters): def read_parameter_file(input_file, parameters: Parameters) -> Parameters:
"""Read a parameter file. """Read a parameter file.
Args: Args:
@@ -144,7 +143,7 @@ def read_parameter_file(input_file, parameters):
return parameters return parameters
def conformation_sorter(conf): def conformation_sorter(conf: str) -> int:
"""TODO - figure out what this function does.""" """TODO - figure out what this function does."""
model = int(conf[:-1]) model = int(conf[:-1])
altloc = conf[-1:] altloc = conf[-1:]
@@ -152,7 +151,7 @@ def conformation_sorter(conf):
def get_atom_lines_from_pdb(pdb_file, ignore_residues=[], keep_protons=False, def get_atom_lines_from_pdb(pdb_file, ignore_residues=[], keep_protons=False,
tags=['ATOM ', 'HETATM'], chains=None): tags=['ATOM ', 'HETATM'], chains=None) -> Iterator[Tuple[str, Atom]]:
"""Get atom lines from PDB file. """Get atom lines from PDB file.
Args: Args:
@@ -237,7 +236,7 @@ def read_pdb(pdb_file, parameters, molecule):
keep_protons=molecule.options.keep_protons, keep_protons=molecule.options.keep_protons,
chains=molecule.options.chains) chains=molecule.options.chains)
for (name, atom) in lines: for (name, atom) in lines:
if not name in conformations.keys(): if name not in conformations.keys():
conformations[name] = ConformationContainer( conformations[name] = ConformationContainer(
name=name, parameters=parameters, molecular_container=molecule) name=name, parameters=parameters, molecular_container=molecule)
conformations[name].add_atom(atom) conformations[name].add_atom(atom)

View File

@@ -6,6 +6,8 @@ Molecular container for storing all contents of PDB files.
""" """
import logging import logging
import os import os
from typing import Dict, List, Optional, Tuple
import propka.version import propka.version
from propka.output import write_pka, print_header, print_result from propka.output import write_pka, print_header, print_result
from propka.conformation_container import ConformationContainer from propka.conformation_container import ConformationContainer
@@ -28,7 +30,12 @@ class MolecularContainer:
PROPKA input files is no longer supported. PROPKA input files is no longer supported.
""" """
def __init__(self, parameters, options=None): conformation_names: List[str]
conformations: Dict[str, ConformationContainer]
name: Optional[str]
version: propka.version.Version
def __init__(self, parameters, options=None) -> None:
"""Initialize molecular container. """Initialize molecular container.
Args: Args:
@@ -50,7 +57,7 @@ class MolecularContainer:
parameters.version) parameters.version)
raise Exception(errstr) raise Exception(errstr)
def top_up_conformations(self): def top_up_conformations(self) -> None:
"""Makes sure that all atoms are present in all conformations.""" """Makes sure that all atoms are present in all conformations."""
ref_atoms = { ref_atoms = {
atom.residue_label: atom atom.residue_label: atom
@@ -60,24 +67,24 @@ class MolecularContainer:
for conf in self.conformations.values(): for conf in self.conformations.values():
conf.top_up_from_atoms(ref_atoms.values()) conf.top_up_from_atoms(ref_atoms.values())
def find_covalently_coupled_groups(self): def find_covalently_coupled_groups(self) -> None:
"""Find covalently coupled groups.""" """Find covalently coupled groups."""
for name in self.conformation_names: for name in self.conformation_names:
self.conformations[name].find_covalently_coupled_groups() self.conformations[name].find_covalently_coupled_groups()
def find_non_covalently_coupled_groups(self): def find_non_covalently_coupled_groups(self) -> None:
"""Find non-covalently coupled groups.""" """Find non-covalently coupled groups."""
verbose = self.options.display_coupled_residues verbose = self.options.display_coupled_residues
for name in self.conformation_names: for name in self.conformation_names:
self.conformations[name].find_non_covalently_coupled_groups( self.conformations[name].find_non_covalently_coupled_groups(
verbose=verbose) verbose=verbose)
def extract_groups(self): def extract_groups(self) -> None:
"""Identify the groups needed for pKa calculation.""" """Identify the groups needed for pKa calculation."""
for name in self.conformation_names: for name in self.conformation_names:
self.conformations[name].extract_groups() self.conformations[name].extract_groups()
def calculate_pka(self): def calculate_pka(self) -> None:
"""Calculate pKa values.""" """Calculate pKa values."""
# calculate for each conformation # calculate for each conformation
for name in self.conformation_names: for name in self.conformation_names:
@@ -90,7 +97,7 @@ class MolecularContainer:
# print out the conformation-average results # print out the conformation-average results
print_result(self, 'AVR', self.version.parameters) print_result(self, 'AVR', self.version.parameters)
def average_of_conformations(self): def average_of_conformations(self) -> None:
"""Generate an average of conformations.""" """Generate an average of conformations."""
parameters = self.conformations[self.conformation_names[0]].parameters parameters = self.conformations[self.conformation_names[0]].parameters
# make a new configuration to hold the average values # make a new configuration to hold the average values
@@ -124,7 +131,7 @@ class MolecularContainer:
self.conformations['AVR'] = avr_conformation self.conformations['AVR'] = avr_conformation
def write_pka(self, filename=None, reference="neutral", def write_pka(self, filename=None, reference="neutral",
direction="folding", options=None): direction="folding", options=None) -> None:
"""Write pKa information to a file. """Write pKa information to a file.
Args: Args:
@@ -187,7 +194,7 @@ class MolecularContainer:
stability_range = [min(stable_values), max(stable_values)] stability_range = [min(stable_values), max(stable_values)]
return profile, opt, range_80pct, stability_range return profile, opt, range_80pct, stability_range
def get_charge_profile(self, conformation='AVR', grid=[0., 14., .1]): def get_charge_profile(self, conformation: str = 'AVR', grid=[0., 14., .1]):
"""Get charge profile for conformation as function of pH. """Get charge profile for conformation as function of pH.
Args: Args:
@@ -196,7 +203,7 @@ class MolecularContainer:
Returns: Returns:
list of charge state values list of charge state values
""" """
charge_profile = [] charge_profile: List[List[float]] = []
for ph in make_grid(*grid): for ph in make_grid(*grid):
conf = self.conformations[conformation] conf = self.conformations[conformation]
q_unfolded, q_folded = conf.calculate_charge( q_unfolded, q_folded = conf.calculate_charge(
@@ -204,8 +211,8 @@ class MolecularContainer:
charge_profile.append([ph, q_unfolded, q_folded]) charge_profile.append([ph, q_unfolded, q_folded])
return charge_profile return charge_profile
def get_pi(self, conformation='AVR', grid=[0., 14., 1], *, def get_pi(self, conformation: str = 'AVR', grid=[0., 14., 1], *,
precision: float = 1e-4): precision: float = 1e-4) -> Tuple[float, float]:
"""Get the isoelectric points for folded and unfolded states. """Get the isoelectric points for folded and unfolded states.
Args: Args:

View File

@@ -6,16 +6,35 @@ Vector algebra for PROPKA.
""" """
import logging import logging
import math import math
from typing import Optional, Protocol, Union
from propka.lib import get_sorted_configurations from propka.lib import get_sorted_configurations
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
class _XYZ(Protocol):
"""
Protocol for types which have x/y/z attributes, like Vector or Atom.
"""
x: float
y: float
z: float
class Vector: class Vector:
"""Vector""" """Vector"""
def __init__(self, xi=0.0, yi=0.0, zi=0.0, atom1=None, atom2=None): x: float
y: float
z: float
def __init__(self,
xi: float = 0.0,
yi: float = 0.0,
zi: float = 0.0,
atom1: Optional[_XYZ] = None,
atom2: Optional[_XYZ] = None):
"""Initialize vector. """Initialize vector.
Args: Args:
@@ -41,17 +60,17 @@ class Vector:
self.y = atom2.y - self.y self.y = atom2.y - self.y
self.z = atom2.z - self.z self.z = atom2.z - self.z
def __add__(self, other): def __add__(self, other: _XYZ):
return Vector(self.x + other.x, return Vector(self.x + other.x,
self.y + other.y, self.y + other.y,
self.z + other.z) self.z + other.z)
def __sub__(self, other): def __sub__(self, other: _XYZ):
return Vector(self.x - other.x, return Vector(self.x - other.x,
self.y - other.y, self.y - other.y,
self.z - other.z) self.z - other.z)
def __mul__(self, other): def __mul__(self, other: Union["Vector", "Matrix4x4", float]):
"""Dot product, scalar and matrix multiplication.""" """Dot product, scalar and matrix multiplication."""
if isinstance(other, Vector): if isinstance(other, Vector):
return self.x * other.x + self.y * other.y + self.z * other.z return self.x * other.x + self.y * other.y + self.z * other.z
@@ -66,14 +85,12 @@ class Vector:
) )
elif type(other) in [int, float]: elif type(other) in [int, float]:
return Vector(self.x * other, self.y * other, self.z * other) return Vector(self.x * other, self.y * other, self.z * other)
else: raise TypeError(f'{type(other)} not supported')
_LOGGER.info('{0:s} not supported'.format(type(other)))
raise TypeError
def __rmul__(self, other): def __rmul__(self, other):
return self.__mul__(other) return self.__mul__(other)
def __pow__(self, other): def __pow__(self, other: _XYZ):
"""Cross product.""" """Cross product."""
return Vector(self.y * other.z - self.z * other.y, return Vector(self.y * other.z - self.z * other.y,
self.z * other.x - self.x * other.z, self.z * other.x - self.x * other.z,
@@ -89,7 +106,7 @@ class Vector:
"""Return vector squared-length""" """Return vector squared-length"""
return self.x * self.x + self.y * self.y + self.z * self.z return self.x * self.x + self.y * self.y + self.z * self.z
def length(self): def length(self) -> float:
"""Return vector length.""" """Return vector length."""
return math.sqrt(self.sq_length()) return math.sqrt(self.sq_length())
@@ -107,7 +124,7 @@ class Vector:
res = Vector(self.z, 0, -self.x) res = Vector(self.z, 0, -self.x)
return res return res
def rescale(self, new_length): def rescale(self, new_length: float):
""" Rescale vector to new length while preserving direction """ """ Rescale vector to new length while preserving direction """
frac = new_length/(self.length()) frac = new_length/(self.length())
res = Vector(xi=self.x*frac, yi=self.y*frac, zi=self.z*frac) res = Vector(xi=self.x*frac, yi=self.y*frac, zi=self.z*frac)
@@ -145,7 +162,7 @@ class Matrix4x4:
self.a44 = a44i self.a44 = a44i
def angle(avec, bvec): def angle(avec: Vector, bvec: Vector) -> float:
"""Get the angle between two vectors. """Get the angle between two vectors.
Args: Args:
@@ -158,7 +175,7 @@ def angle(avec, bvec):
return math.acos(dot / (avec.length() * bvec.length())) return math.acos(dot / (avec.length() * bvec.length()))
def angle_degrees(avec, bvec): def angle_degrees(avec: Vector, bvec: Vector) -> float:
"""Get the angle between two vectors in degrees. """Get the angle between two vectors in degrees.
Args: Args:
@@ -170,7 +187,7 @@ def angle_degrees(avec, bvec):
return math.degrees(angle(avec, bvec)) return math.degrees(angle(avec, bvec))
def signed_angle_around_axis(avec, bvec, axis): def signed_angle_around_axis(avec: Vector, bvec: Vector, axis: Vector) -> float:
"""Get signed angle of two vectors around axis in radians. """Get signed angle of two vectors around axis in radians.
Args: Args:
@@ -189,7 +206,7 @@ def signed_angle_around_axis(avec, bvec, axis):
return ang return ang
def rotate_vector_around_an_axis(theta, axis, vec): def rotate_vector_around_an_axis(theta: float, axis: Vector, vec: Vector) -> Vector:
"""Rotate vector around an axis. """Rotate vector around an axis.
Args: Args:
@@ -225,7 +242,7 @@ def rotate_vector_around_an_axis(theta, axis, vec):
return vec return vec
def rotate_atoms_around_z_axis(theta): def rotate_atoms_around_z_axis(theta: float) -> Matrix4x4:
"""Get rotation matrix for z-axis. """Get rotation matrix for z-axis.
Args: Args:
@@ -253,7 +270,7 @@ def rotate_atoms_around_z_axis(theta):
) )
def rotate_atoms_around_y_axis(theta): def rotate_atoms_around_y_axis(theta: float) -> Matrix4x4:
"""Get rotation matrix for y-axis. """Get rotation matrix for y-axis.
Args: Args:

View File

@@ -19,3 +19,15 @@ omit =
exclude_lines = exclude_lines =
pragma: no cover pragma: no cover
[yapf]
column_limit = 88
based_on_style = pep8
allow_split_before_dict_value = False
[mypy]
files = propka,tests
exclude = (?x)(
/_version\.py$
)
explicit_package_bases = True
ignore_missing_imports = True

View File

@@ -10,6 +10,7 @@ from propka.parameters import Parameters
from propka.molecular_container import MolecularContainer from propka.molecular_container import MolecularContainer
from propka.input import read_parameter_file, read_molecule_file from propka.input import read_parameter_file, read_molecule_file
from propka.lib import loadOptions from propka.lib import loadOptions
from typing import List
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -32,8 +33,6 @@ RESULTS_DIR = Path("tests/results")
if not RESULTS_DIR.is_dir(): if not RESULTS_DIR.is_dir():
_LOGGER.warning("Switching to sub-directory") _LOGGER.warning("Switching to sub-directory")
RESULTS_DIR = Path("results") RESULTS_DIR = Path("results")
# Arguments to add to all tests
DEFAULT_ARGS = []
def get_test_dirs(): def get_test_dirs():
@@ -87,8 +86,8 @@ def run_propka(options, pdb_path, tmp_path):
def parse_pka(pka_path: Path) -> dict: def parse_pka(pka_path: Path) -> dict:
"""Parse testable data from a .pka file into a dictionary. """Parse testable data from a .pka file into a dictionary.
""" """
pka_list = [] pka_list: List[float] = []
data = {"pKa": pka_list} data: dict = {"pKa": pka_list}
with open(pka_path, "rt") as pka_file: with open(pka_path, "rt") as pka_file:
at_pka = False at_pka = False
@@ -98,6 +97,7 @@ def parse_pka(pka_path: Path) -> dict:
at_pka = False at_pka = False
else: else:
m = re.search(r'\d+\.\d+', line[13:]) m = re.search(r'\d+\.\d+', line[13:])
assert m is not None
pka_list.append(float(m.group())) pka_list.append(float(m.group()))
elif "model-pKa" in line: elif "model-pKa" in line:
at_pka = True at_pka = True