Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More static typing #177

Merged
merged 6 commits into from
Dec 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@ include propka.cfg

include versioneer.py
include propka/_version.py

# Python type annotation declarations, see PEP-561
include propka/py.typed
92 changes: 52 additions & 40 deletions propka/atom.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import string
from typing import cast, List, NoReturn, Optional, TYPE_CHECKING
import warnings

from propka.lib import make_tidy_atom_label
from . import hybrid36
Expand Down Expand Up @@ -42,6 +43,43 @@
:meth:`make_input_line` and :meth:`get_input_parameters` have been
removed as reading/writing PROPKA input is no longer supported.
"""
group: Optional["Group"] = None
group_type: Optional[str] = None
cysteine_bridge: bool = False
residue: NoReturn = None # type: ignore[assignment]
conformation_container: Optional["ConformationContainer"] = None
molecular_container: Optional["MolecularContainer"] = None
is_protonated: bool = False
steric_num_lone_pairs_set: bool = False
terminal: Optional[str] = None
charge: float = 0.0
charge_set: bool = False
steric_number: int = 0
number_of_lone_pairs: int = 0
number_of_protons_to_add: int = 0
num_pi_elec_2_3_bonds: int = 0
num_pi_elec_conj_2_3_bonds: int = 0
groups_extracted: bool = False

# PDB attributes
name: str = ''
numb: int = 0
x: float = 0.0
y: float = 0.0
z: float = 0.0
res_num: int = 0
res_name: str = ''
chain_id: str = 'A'
type: str = ''
occ: str = '1.0'
beta: str = '0.0'
element: str = ''
icode: str = ''

# ligand atom types
sybyl_type = ''
sybyl_assigned = False
marvin_pka = False

def __init__(self, line: Optional[str] = None):
"""Initialize Atom object.
Expand All @@ -50,53 +88,17 @@
line: Line from a PDB file to set properties of atom.
"""
self.number_of_bonded_elements: NoReturn = cast(NoReturn, {}) # FIXME unused?
self.group: Optional[Group] = None
self.group_type: Optional[str] = None
self.cysteine_bridge: bool = False
self.bonded_atoms: List[Atom] = []
self.residue = None
self.conformation_container: Optional[ConformationContainer] = None
self.molecular_container: Optional[MolecularContainer] = None
self.is_protonated = False
self.steric_num_lone_pairs_set = False
self.terminal: Optional[str] = None
self.charge = 0.0
self.charge_set = False
self.steric_number = 0
self.number_of_lone_pairs = 0
self.number_of_protons_to_add = 0
self.num_pi_elec_2_3_bonds = 0
self.num_pi_elec_conj_2_3_bonds = 0
self.groups_extracted = 0
self.set_properties(line)
fmt = "{r.name:3s}{r.res_num:>4d}{r.chain_id:>2s}"
self.residue_label = fmt.format(r=self)

# ligand atom types
self.sybyl_type = ''
self.sybyl_assigned = False
self.marvin_pka = False

def set_properties(self, line: Optional[str]):
"""Line from PDB file to set properties of atom.

Args:
line: PDB file line
"""
self.name = ''
self.numb = 0
self.x = 0.0
self.y = 0.0
self.z = 0.0
self.res_num = 0
self.res_name = ''
self.chain_id = 'A'
self.type = ''
self.occ = '1.0'
self.beta = '0.0'
self.element = ''
self.icode = ''

if line:
self.name = line[12:16].strip()
self.numb = int(hybrid36.decode(line[6:11]))
Expand Down Expand Up @@ -183,9 +185,17 @@
return True
return False

def set_property(self, numb=None, name=None, res_name=None, chain_id=None,
res_num=None, x=None, y=None, z=None, occ=None,
beta=None):
def set_property(self,
numb: Optional[int] = None,
name: Optional[str] = None,
res_name: Optional[str] = None,
chain_id: Optional[str] = None,
res_num: Optional[int] = None,
x: Optional[float] = None,
y: Optional[float] = None,
z: Optional[float] = None,
occ: Optional[str] = None,
beta: Optional[str] = None):
"""Set properties of the atom object.

Args:
Expand Down Expand Up @@ -303,6 +313,7 @@
Returns:
String with PDB line.
"""
warnings.warn("only used by unused function")

Check warning on line 316 in propka/atom.py

View check run for this annotation

Codecov / codecov/patch

propka/atom.py#L316

Added line #L316 was not covered by tests
if numb is None:
numb = self.numb
if name is None:
Expand Down Expand Up @@ -343,11 +354,12 @@
"""Return an undefined-format string version of this atom."""
return STR_FMT.format(r=self)

def set_residue(self, residue):
def set_residue(self, residue: NoReturn):
""" Makes a reference to the parent residue

Args:
residue: the parent residue
"""
raise NotImplementedError("unused")

Check warning on line 363 in propka/atom.py

View check run for this annotation

Codecov / codecov/patch

propka/atom.py#L363

Added line #L363 was not covered by tests
if self.residue is None:
self.residue = residue
2 changes: 2 additions & 0 deletions propka/bonds.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
Args:
protein: the protein to search for bonds
"""
raise NotImplementedError("unused")

Check warning on line 96 in propka/bonds.py

View check run for this annotation

Codecov / codecov/patch

propka/bonds.py#L96

Added line #L96 was not covered by tests
_LOGGER.info('++++ Side chains ++++')
# side chains
for chain in protein.chains:
Expand Down Expand Up @@ -132,6 +133,7 @@
cys1: one of the cysteines to check
cys1: one of the cysteines to check
"""
raise NotImplementedError("unused")

Check warning on line 136 in propka/bonds.py

View check run for this annotation

Codecov / codecov/patch

propka/bonds.py#L136

Added line #L136 was not covered by tests
for atom1 in cys1.atoms:
if atom1.name == 'SG':
for atom2 in cys2.atoms:
Expand Down
40 changes: 26 additions & 14 deletions propka/conformation_container.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
"""
import logging
import functools
from typing import Iterable, List, NoReturn, Optional, TYPE_CHECKING, Set
from typing import Callable, Dict, Iterable, Iterator, List, NoReturn, Optional, TYPE_CHECKING, Set

from propka.lib import Options
from propka.version import Version

if TYPE_CHECKING:
from propka.atom import Atom
Expand All @@ -19,10 +22,13 @@
from propka.determinants import set_backbone_determinants, set_ion_determinants
from propka.determinants import set_determinants
from propka.group import Group, is_group
from propka.parameters import Parameters


_LOGGER = logging.getLogger(__name__)

CallableGroupToGroups = Callable[[Group], List[Group]]


#: A large number that gets multipled with the integer obtained from applying
#: :func:`ord` to the atom chain ID. Used in calculating atom keys for
Expand All @@ -44,9 +50,9 @@
"""

def __init__(self,
name: str = '',
parameters=None,
molecular_container: Optional["MolecularContainer"] = None):
name: str,
parameters: Parameters,
molecular_container: "MolecularContainer"):
"""Initialize conformation container.

Args:
Expand Down Expand Up @@ -145,6 +151,7 @@
Returns:
a set of bonded atom groups
"""
assert self.parameters is not None
res: Set[Group] = set()
for bond_atom in atom.bonded_atoms:
# skip the original atom
Expand Down Expand Up @@ -187,7 +194,7 @@

# If --titrate_only option is set, make non-specified residues
# un-titratable:
assert self.molecular_container is not None
assert self.molecular_container.options is not None
titrate_only = self.molecular_container.options.titrate_only
if titrate_only is not None:
atom = group.atom
Expand All @@ -196,7 +203,7 @@
if group.residue_type == 'CYS':
group.exclude_cys_from_results = True

def calculate_pka(self, version, options):
def calculate_pka(self, version: Version, options: Options):
"""Calculate pKas for conformation container.

Args:
Expand Down Expand Up @@ -272,7 +279,7 @@
return penalised_labels

@staticmethod
def share_determinants(groups):
def share_determinants(groups: Iterable[Group]):
"""Share sidechain, backbone, and Coloumb determinants between groups.

Args:
Expand All @@ -282,7 +289,7 @@
types = ['sidechain', 'backbone', 'coulomb']
for type_ in types:
# find maximum value for each determinant
max_dets = {}
max_dets: Dict[Group, float] = {}

Check warning on line 292 in propka/conformation_container.py

View check run for this annotation

Codecov / codecov/patch

propka/conformation_container.py#L292

Added line #L292 was not covered by tests
for group in groups:
for det in group.determinants[type_]:
# update max dets
Expand All @@ -298,7 +305,11 @@
for group in groups:
group.set_determinant(new_determinant, type_)

def get_coupled_systems(self, groups, get_coupled_groups):
def get_coupled_systems(
self,
groups: Iterable[Group],
get_coupled_groups: CallableGroupToGroups,
) -> Iterator[Set[Group]]:
"""A generator that yields covalently coupled systems.

Args:
Expand All @@ -310,15 +321,16 @@
groups = set(groups)
while len(groups) > 0:
# extract a system of coupled groups ...
system = set()
system: Set[Group] = set()
self.get_a_coupled_system_of_groups(
groups.pop(), system, get_coupled_groups)
# ... and remove them from the list
groups -= system
yield system

def get_a_coupled_system_of_groups(self, new_group, coupled_groups,
get_coupled_groups):
def get_a_coupled_system_of_groups(self, new_group: Group,
coupled_groups: Set[Group],
get_coupled_groups: CallableGroupToGroups):
"""Set up coupled systems of groups.

Args:
Expand Down Expand Up @@ -349,7 +361,7 @@
reference=reference)
return ddg

def calculate_charge(self, parameters, ph=None):
def calculate_charge(self, parameters: Parameters, ph: float):
"""Calculate charge for folded and unfolded states.

Args:
Expand All @@ -367,7 +379,7 @@
state='folded')
return unfolded, folded

def get_backbone_groups(self):
def get_backbone_groups(self) -> List[Group]:
"""Get backbone groups needed for the pKa calculations.

Returns:
Expand Down
13 changes: 9 additions & 4 deletions propka/coupled_groups.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,20 @@
"""
import logging
import itertools
from typing import Optional
import propka.lib
from propka.group import Group
from propka.output import make_interaction_map
from propka.parameters import Parameters


_LOGGER = logging.getLogger(__name__)


class NonCovalentlyCoupledGroups:
"""Groups that are coupled without covalent bonding."""
def __init__(self):
self.parameters = None
self.do_prot_stat = True
parameters: Optional[Parameters] = None
do_prot_stat = True

def is_coupled_protonation_state_probability(self, group1, group2,
energy_method,
Expand All @@ -33,6 +34,7 @@ def is_coupled_protonation_state_probability(self, group1, group2,
Returns:
dictionary describing coupling
"""
assert self.parameters is not None
# check if the interaction energy is high enough
interaction_energy = max(self.get_interaction(group1, group2),
self.get_interaction(group2, group1))
Expand Down Expand Up @@ -105,6 +107,7 @@ def get_pka_diff_factor(self, pka1, pka2):
Returns:
float value of scaling factor
"""
assert self.parameters is not None
intrinsic_pka_diff = abs(pka1-pka2)
res = 0.0
if intrinsic_pka_diff <= self.parameters.max_intrinsic_pka_diff:
Expand All @@ -122,6 +125,7 @@ def get_free_energy_diff_factor(self, energy1, energy2):
Returns:
float value of scaling factor
"""
assert self.parameters is not None
free_energy_diff = abs(energy1-energy2)
res = 0.0
if free_energy_diff <= self.parameters.max_free_energy_diff:
Expand All @@ -136,6 +140,7 @@ def get_interaction_factor(self, interaction_energy):
Returns:
float value of scaling factor
"""
assert self.parameters is not None
res = 0.0
interaction_energy = abs(interaction_energy)
if interaction_energy >= self.parameters.min_interaction_energy:
Expand Down Expand Up @@ -260,7 +265,7 @@ def print_system(self, conformation, system):
_LOGGER.info(swap_info)

@staticmethod
def get_interaction(group1, group2, include_side_chain_hbs=True):
def get_interaction(group1: Group, group2: Group, include_side_chain_hbs=True):
"""Get interaction energy between two groups.

Args:
Expand Down
Loading
Loading