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

Add type annotations and mypy CI job #172

Merged
merged 1 commit into from
Nov 3, 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
10 changes: 10 additions & 0 deletions .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,13 @@ jobs:
with:
name: coverage-html
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 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@
docs/build
docs/source/api/*.rst
build/
dist/
.coverage
47 changes: 19 additions & 28 deletions propka/atom.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,15 @@
"""

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

from propka.lib import make_tidy_atom_label
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)
PDB_LINE_FMT1 = (
Expand Down Expand Up @@ -37,37 +43,24 @@ class Atom:
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.

Args:
line: Line from a PDB file to set properties of atom.
"""
self.occ = None
self.numb = None
self.res_name = None
self.type = None
self.chain_id = None
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.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 = None
self.molecular_container = 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 = None
self.charge = 0
self.terminal: Optional[str] = None
self.charge = 0.0
self.charge_set = False
self.steric_number = 0
self.number_of_lone_pairs = 0
Expand All @@ -84,7 +77,7 @@ def __init__(self, line=None):
self.sybyl_assigned = 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.

Args:
Expand Down Expand Up @@ -112,10 +105,8 @@ def set_properties(self, line):
self.z = float(line[46:54].strip())
self.res_num = int(line[22:26].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.
if not self.chain_id.strip():
self.chain_id = '_'
self.chain_id = line[21].strip() or '_'
self.type = line[:6].strip().lower()

# TODO - define nucleic acid residue names elsewhere
Expand All @@ -134,7 +125,7 @@ def set_properties(self, line):
self.element = '{0:1s}{1:1s}'.format(
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.

Args:
Expand Down
6 changes: 5 additions & 1 deletion propka/bonds.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
import json
import pkg_resources
import propka.calculations
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from propka.molecular_container import MolecularContainer


_LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -329,7 +333,7 @@ def check_distance(self, atom1, atom2):
return True
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.
Args:
Expand Down
15 changes: 11 additions & 4 deletions propka/calculations.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@
"""

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
MAX_DISTANCE = 1e6


def squared_distance(atom1, atom2):
def squared_distance(atom1: _XYZ, atom2: _XYZ) -> float:
"""Calculate the squared distance between two atoms.

Args:
Expand All @@ -28,7 +32,7 @@ def squared_distance(atom1, atom2):
return res


def distance(atom1, atom2):
def distance(atom1: _XYZ, atom2: _XYZ) -> float:
"""Calculate the distance between two atoms.

Args:
Expand All @@ -40,7 +44,10 @@ def 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.

Args:
Expand All @@ -59,4 +66,4 @@ def get_smallest_distance(atoms1, atoms2):
res_dist = dist
res_atom1 = atom1
res_atom2 = atom2
return [res_atom1, math.sqrt(res_dist), res_atom2]
return (res_atom1, math.sqrt(res_dist), res_atom2)
46 changes: 29 additions & 17 deletions propka/conformation_container.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,19 @@
"""
import logging
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
from propka.output import make_interaction_map
from propka.determinant import Determinant
from propka.coupled_groups import NCCG
from propka.determinants import set_backbone_determinants, set_ion_determinants
from propka.determinants import set_determinants
from propka.group import Group, is_group
from typing import Iterable


_LOGGER = logging.getLogger(__name__)
Expand All @@ -38,7 +43,10 @@ class ConformationContainer:
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.

Args:
Expand All @@ -49,9 +57,9 @@ def __init__(self, name='', parameters=None, molecular_container=None):
self.molecular_container = molecular_container
self.name = name
self.parameters = parameters
self.atoms = []
self.groups = []
self.chains = []
self.atoms: List["Atom"] = []
self.groups: List[Group] = []
self.chains: List[str] = []
self.current_iter_item = 0
self.marvin_pkas_calculated = False
self.non_covalently_coupled_groups = False
Expand Down Expand Up @@ -126,7 +134,8 @@ def find_non_covalently_coupled_groups(self, verbose=False):
self.get_titratable_groups()))) > 0:
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.

Args:
Expand All @@ -136,7 +145,7 @@ def find_bonded_titratable_groups(self, atom, num_bonds, original_atom):
Returns:
a set of bonded atom groups
"""
res = set()
res: Set[Group] = set()
for bond_atom in atom.bonded_atoms:
# skip the original atom
if bond_atom == original_atom:
Expand All @@ -152,7 +161,7 @@ def find_bonded_titratable_groups(self, atom, num_bonds, original_atom):
bond_atom, num_bonds+1, original_atom)
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.

Args:
Expand All @@ -166,7 +175,7 @@ def setup_and_add_group(self, group):
self.init_group(group)
self.groups.append(group)

def init_group(self, group):
def init_group(self, group: Group):
"""Initialize the given Group object.

Args:
Expand All @@ -178,10 +187,11 @@ def init_group(self, group):

# If --titrate_only option is set, make non-specified residues
# un-titratable:
assert self.molecular_container is not None
titrate_only = self.molecular_container.options.titrate_only
if titrate_only is not None:
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
if group.residue_type == 'CYS':
group.exclude_cys_from_results = True
Expand Down Expand Up @@ -475,25 +485,27 @@ def get_ions(self):
group for group in self.groups
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.

Args:
group_list: list to check
Returns:
list of groups
"""
if TYPE_CHECKING:
assert False
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.

Returns:
list of atoms
"""
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.

Returns:
Expand All @@ -503,7 +515,7 @@ def get_heavy_ligand_atoms(self):
atom for atom in self.atoms
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.

Args:
Expand All @@ -513,7 +525,7 @@ def get_chain(self, 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.

Args:
Expand Down Expand Up @@ -556,7 +568,7 @@ def top_up(self, other):
"""
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.

Args:
Expand Down Expand Up @@ -613,7 +625,7 @@ def sort_atoms(self):
self.atoms[i].numb = i+1

@staticmethod
def sort_atoms_key(atom):
def sort_atoms_key(atom: "Atom") -> float:
"""Generate key for atom sorting.

Args:
Expand Down
Loading
Loading