Typing: Options

This commit is contained in:
Thomas Holder
2023-12-13 15:18:39 +01:00
parent 723609cc33
commit 0aafca7f73
10 changed files with 184 additions and 92 deletions

View File

@@ -6,7 +6,8 @@ Container data structure for molecular conformations.
""" """
import logging import logging
import functools 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.lib import Options
from propka.version import Version from propka.version import Version
@@ -26,6 +27,8 @@ from propka.parameters import Parameters
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
CallableGroupToGroups = Callable[[Group], List[Group]]
#: A large number that gets multipled with the integer obtained from applying #: 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 #: :func:`ord` to the atom chain ID. Used in calculating atom keys for
@@ -276,7 +279,7 @@ class ConformationContainer:
return penalised_labels return penalised_labels
@staticmethod @staticmethod
def share_determinants(groups): def share_determinants(groups: Iterable[Group]):
"""Share sidechain, backbone, and Coloumb determinants between groups. """Share sidechain, backbone, and Coloumb determinants between groups.
Args: Args:
@@ -286,7 +289,7 @@ class ConformationContainer:
types = ['sidechain', 'backbone', 'coulomb'] types = ['sidechain', 'backbone', 'coulomb']
for type_ in types: for type_ in types:
# find maximum value for each determinant # find maximum value for each determinant
max_dets = {} max_dets: Dict[Group, float] = {}
for group in groups: for group in groups:
for det in group.determinants[type_]: for det in group.determinants[type_]:
# update max dets # update max dets
@@ -302,7 +305,11 @@ class ConformationContainer:
for group in groups: for group in groups:
group.set_determinant(new_determinant, type_) 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. """A generator that yields covalently coupled systems.
Args: Args:
@@ -314,15 +321,16 @@ class ConformationContainer:
groups = set(groups) groups = set(groups)
while len(groups) > 0: while len(groups) > 0:
# extract a system of coupled groups ... # extract a system of coupled groups ...
system = set() system: Set[Group] = set()
self.get_a_coupled_system_of_groups( self.get_a_coupled_system_of_groups(
groups.pop(), system, get_coupled_groups) groups.pop(), system, get_coupled_groups)
# ... and remove them from the list # ... and remove them from the list
groups -= system groups -= system
yield system yield system
def get_a_coupled_system_of_groups(self, new_group, coupled_groups, def get_a_coupled_system_of_groups(self, new_group: Group,
get_coupled_groups): coupled_groups: Set[Group],
get_coupled_groups: CallableGroupToGroups):
"""Set up coupled systems of groups. """Set up coupled systems of groups.
Args: Args:
@@ -353,7 +361,7 @@ class ConformationContainer:
reference=reference) reference=reference)
return ddg return ddg
def calculate_charge(self, parameters, ph: float): def calculate_charge(self, parameters: Parameters, ph: float):
"""Calculate charge for folded and unfolded states. """Calculate charge for folded and unfolded states.
Args: Args:
@@ -371,7 +379,7 @@ class ConformationContainer:
state='folded') state='folded')
return unfolded, folded return unfolded, folded
def get_backbone_groups(self): def get_backbone_groups(self) -> List[Group]:
"""Get backbone groups needed for the pKa calculations. """Get backbone groups needed for the pKa calculations.
Returns: Returns:

View File

@@ -6,9 +6,11 @@ Describe and analyze energetic coupling between groups.
""" """
import logging import logging
import itertools import itertools
from typing import Optional
import propka.lib import propka.lib
from propka.group import Group from propka.group import Group
from propka.output import make_interaction_map from propka.output import make_interaction_map
from propka.parameters import Parameters
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -16,9 +18,8 @@ _LOGGER = logging.getLogger(__name__)
class NonCovalentlyCoupledGroups: class NonCovalentlyCoupledGroups:
"""Groups that are coupled without covalent bonding.""" """Groups that are coupled without covalent bonding."""
def __init__(self): parameters: Optional[Parameters] = None
self.parameters = None do_prot_stat = True
self.do_prot_stat = True
def is_coupled_protonation_state_probability(self, group1, group2, def is_coupled_protonation_state_probability(self, group1, group2,
energy_method, energy_method,
@@ -264,7 +265,7 @@ class NonCovalentlyCoupledGroups:
_LOGGER.info(swap_info) _LOGGER.info(swap_info)
@staticmethod @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. """Get interaction energy between two groups.
Args: Args:

View File

@@ -15,6 +15,11 @@ Provides the :class:`Determinant` class.
""" """
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from propka.group import Group
class Determinant: class Determinant:
"""Determinant class. """Determinant class.
@@ -25,7 +30,7 @@ class Determinant:
TODO - figure out what this class does. TODO - figure out what this class does.
""" """
def __init__(self, group, value): def __init__(self, group: "Group", value: float):
"""Initialize the object. """Initialize the object.
Args: Args:
@@ -36,7 +41,7 @@ class Determinant:
self.label = group.label self.label = group.label
self.value = value self.value = value
def add(self, value): def add(self, value: float):
"""Increment determinant value. """Increment determinant value.
Args: Args:

View File

@@ -9,8 +9,7 @@ Input routines.
Methods to read PROPKA input files (:func:`read_propka` and Methods to read PROPKA input files (:func:`read_propka` and
:func:`get_atom_lines_from_input`) have been removed. :func:`get_atom_lines_from_input`) have been removed.
""" """
import typing from typing import IO, ContextManager, Dict, Iterable, Iterator, Optional, Tuple
from typing import Iterator, Tuple, Union
import contextlib import contextlib
import io import io
import zipfile import zipfile
@@ -19,19 +18,18 @@ 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.molecular_container import MolecularContainer
from propka.output import _PathArg, _PathLikeTypes, _TextIOSource
from propka.parameters import Parameters from propka.parameters import Parameters
def open_file_for_reading( def open_file_for_reading(input_file: _TextIOSource) -> ContextManager[IO[str]]:
input_file: typing.Union[str, Path, typing.TextIO]
) -> typing.ContextManager[typing.TextIO]:
"""Open file or file-like stream for reading. """Open file or file-like stream for reading.
Args: Args:
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).
""" """
if not isinstance(input_file, (str, Path)): if not isinstance(input_file, _PathLikeTypes):
input_file.seek(0) input_file.seek(0)
return contextlib.nullcontext(input_file) return contextlib.nullcontext(input_file)
@@ -48,7 +46,11 @@ def open_file_for_reading(
return contextlib.closing(open(input_file, 'rt')) return contextlib.closing(open(input_file, 'rt'))
def read_molecule_file(filename: str, mol_container: MolecularContainer, stream=None) -> MolecularContainer: def read_molecule_file(
filename: _PathArg,
mol_container: MolecularContainer,
stream: Optional[IO[str]] = 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:
@@ -96,11 +98,7 @@ def read_molecule_file(filename: str, mol_container: MolecularContainer, stream=
input_path = Path(filename) input_path = Path(filename)
mol_container.name = input_path.stem mol_container.name = input_path.stem
input_file_extension = input_path.suffix input_file_extension = input_path.suffix
input_file = filename if stream is None else stream
if stream is not None:
input_file = stream
else:
input_file = filename
if input_file_extension.lower() == '.pdb': if input_file_extension.lower() == '.pdb':
# input is a pdb file. read in atoms and top up containers to make # input is a pdb file. read in atoms and top up containers to make
@@ -133,7 +131,7 @@ def read_molecule_file(filename: str, mol_container: MolecularContainer, stream=
return mol_container return mol_container
def read_parameter_file(input_file: Union[Path, str], parameters: Parameters) -> Parameters: def read_parameter_file(input_file: _PathArg, parameters: Parameters) -> Parameters:
"""Read a parameter file. """Read a parameter file.
Args: Args:
@@ -161,8 +159,13 @@ def conformation_sorter(conf: str) -> int:
return model*100+ord(altloc) return model*100+ord(altloc)
def get_atom_lines_from_pdb(pdb_file, ignore_residues=[], keep_protons=False, def get_atom_lines_from_pdb(
tags=['ATOM ', 'HETATM'], chains=None) -> Iterator[Tuple[str, Atom]]: pdb_file: _TextIOSource,
ignore_residues: Iterable[str] = (),
keep_protons: bool = False,
tags: Iterable[str] = ('ATOM ', 'HETATM'),
chains: Optional[Iterable[str]] = None,
) -> Iterator[Tuple[str, Atom]]:
"""Get atom lines from PDB file. """Get atom lines from PDB file.
Args: Args:
@@ -228,7 +231,8 @@ def get_atom_lines_from_pdb(pdb_file, ignore_residues=[], keep_protons=False,
terminal = None terminal = None
def read_pdb(pdb_file, parameters, molecule): def read_pdb(pdb_file: _TextIOSource, parameters: Parameters,
molecule: MolecularContainer):
"""Parse a PDB file. """Parse a PDB file.
Args: Args:
@@ -240,7 +244,7 @@ def read_pdb(pdb_file, parameters, molecule):
1. list of conformations 1. list of conformations
2. list of names 2. list of names
""" """
conformations = {} conformations: Dict[str, ConformationContainer] = {}
# read in all atoms in the file # read in all atoms in the file
lines = get_atom_lines_from_pdb( lines = get_atom_lines_from_pdb(
pdb_file, ignore_residues=parameters.ignore_residues, pdb_file, ignore_residues=parameters.ignore_residues,
@@ -253,4 +257,4 @@ def read_pdb(pdb_file, parameters, molecule):
conformations[name].add_atom(atom) conformations[name].add_atom(atom)
# make a sorted list of conformation names # make a sorted list of conformation names
names = sorted(conformations.keys(), key=conformation_sorter) names = sorted(conformations.keys(), key=conformation_sorter)
return [conformations, names] return conformations, names

View File

@@ -304,7 +304,7 @@ def add_determinants(iterative_interactions: List[Interaction], version: Version
for itres in iteratives: for itres in iteratives:
for type_ in ['sidechain', 'backbone', 'coulomb']: for type_ in ['sidechain', 'backbone', 'coulomb']:
for interaction in itres.determinants[type_]: for interaction in itres.determinants[type_]:
value = interaction[1] value: float = interaction[1]
if value > UNK_MIN_VALUE or value < -UNK_MIN_VALUE: if value > UNK_MIN_VALUE or value < -UNK_MIN_VALUE:
group = interaction[0] group = interaction[0]
new_det = Determinant(group, value) new_det = Determinant(group, value)

View File

@@ -5,16 +5,20 @@ Set-up of a PROPKA calculation
Implements many of the main functions used to call PROPKA. Implements many of the main functions used to call PROPKA.
""" """
import sys
import logging import logging
import argparse import argparse
from pathlib import Path from pathlib import Path
from typing import List, TYPE_CHECKING from typing import Iterable, Iterator, List, TYPE_CHECKING, NoReturn, Optional, Tuple, TypeVar
if TYPE_CHECKING: if TYPE_CHECKING:
from propka.atom import Atom from propka.atom import Atom
T = TypeVar("T")
Number = TypeVar("Number", int, float)
_T_RESIDUE_TUPLE = Tuple[str, int, str]
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -23,7 +27,28 @@ EXPECTED_ATOM_NUMBERS = {'ALA': 5, 'ARG': 11, 'ASN': 8, 'ASP': 8, 'CYS': 6,
'LEU': 8, 'LYS': 9, 'MET': 8, 'PHE': 11, 'PRO': 7, 'LEU': 8, 'LYS': 9, 'MET': 8, 'PHE': 11, 'PRO': 7,
'SER': 6, 'THR': 7, 'TRP': 14, 'TYR': 12, 'VAL': 7} 'SER': 6, 'THR': 7, 'TRP': 14, 'TYR': 12, 'VAL': 7}
Options = argparse.Namespace
class Options:
# Note: All the "NoReturn" members appear to be unused
alignment: NoReturn # Optional[List[str]]
chains: Optional[List[str]]
display_coupled_residues: bool = False
filenames: List[str] # List[Path]?
grid: Tuple[float, float, float] = (0.0, 14.0, 0.1)
input_pdb: str # Path?
keep_protons: bool = False
log_level: str = 'INFO'
mutations: NoReturn # Optional[List[str]]
mutator: NoReturn # Optional[str] # alignment/scwrl/jackal
mutator_options: NoReturn # Optional[List[str]]
pH: NoReturn # float = 7.0
parameters: Path
protonate_all: bool = False
reference: NoReturn # str = 'neutral'
reuse_ligand_mol2_file: bool = False # only used by unused function
thermophiles: NoReturn # Optional[List[str]]
titrate_only: Optional[List[_T_RESIDUE_TUPLE]]
window: Tuple[float, float, float] = (0.0, 14.0, 1.0)
def protein_precheck(conformations, names): def protein_precheck(conformations, names):
@@ -112,11 +137,11 @@ def make_molecule(atom: "Atom", atoms: List["Atom"]):
return res_atoms return res_atoms
def make_grid(min_, max_, step): def make_grid(min_: Number, max_: Number, step: Number) -> Iterator[Number]:
"""Make a grid across the specified tange. """Make a grid across the specified tange.
TODO - figure out if this duplicates existing generators like `range` or Like range() for integers or numpy.arange() for floats, except that `max_`
numpy function. is not excluded from the range.
Args: Args:
min_: minimum value of grid min_: minimum value of grid
@@ -129,7 +154,7 @@ def make_grid(min_, max_, step):
x += step x += step
def generate_combinations(interactions): def generate_combinations(interactions: Iterable[T]) -> List[List[T]]:
"""Generate combinations of interactions. """Generate combinations of interactions.
Args: Args:
@@ -137,14 +162,14 @@ def generate_combinations(interactions):
Returns: Returns:
list of combinations list of combinations
""" """
res = [[]] res: List[List[T]] = [[]]
for interaction in interactions: for interaction in interactions:
res = make_combination(res, interaction) res = make_combination(res, interaction)
res.remove([]) res.remove([])
return res return res
def make_combination(combis, interaction): def make_combination(combis: List[List[T]], interaction: T) -> List[List[T]]:
"""Make a specific set of combinations. """Make a specific set of combinations.
Args: Args:
@@ -160,7 +185,7 @@ def make_combination(combis, interaction):
return res return res
def parse_res_string(res_str): def parse_res_string(res_str: str) -> _T_RESIDUE_TUPLE:
"""Parse a residue string. """Parse a residue string.
Args: Args:
@@ -189,6 +214,16 @@ def parse_res_string(res_str):
return chain, resnum, inscode return chain, resnum, inscode
def parse_res_list(titrate_only: str):
res_list: List[_T_RESIDUE_TUPLE] = []
for res_str in titrate_only.split(','):
try:
res_list.append(parse_res_string(res_str))
except ValueError as ex:
raise argparse.ArgumentTypeError(f'{ex}: "{res_str:s}"')
return res_list
def build_parser(parser=None): def build_parser(parser=None):
"""Build an argument parser for PROPKA. """Build an argument parser for PROPKA.
@@ -233,6 +268,7 @@ def build_parser(parser=None):
'" " for chains without ID [all]')) '" " for chains without ID [all]'))
group.add_argument( group.add_argument(
"-i", "--titrate_only", dest="titrate_only", "-i", "--titrate_only", dest="titrate_only",
type=parse_res_list,
help=('Treat only the specified residues as titratable. Value should ' help=('Treat only the specified residues as titratable. Value should '
'be a comma-separated list of "chain:resnum" values; for ' 'be a comma-separated list of "chain:resnum" values; for '
'example: -i "A:10,A:11"')) 'example: -i "A:10,A:11"'))
@@ -252,8 +288,8 @@ def build_parser(parser=None):
"--version", action="version", version=f"%(prog)s {propka.__version__}") "--version", action="version", version=f"%(prog)s {propka.__version__}")
group.add_argument( group.add_argument(
"-p", "--parameters", dest="parameters", "-p", "--parameters", dest="parameters",
default=str(Path(__file__).parent / "propka.cfg"), type=Path, default=Path(__file__).parent / "propka.cfg",
help="set the parameter file [{default:s}]") help="set the parameter file")
try: try:
group.add_argument( group.add_argument(
"--log-level", "--log-level",
@@ -325,18 +361,6 @@ def loadOptions(args=None) -> Options:
# adding specified filenames to arguments # adding specified filenames to arguments
options.filenames.append(options.input_pdb) options.filenames.append(options.input_pdb)
# Convert titrate_only string to a list of (chain, resnum) items:
if options.titrate_only is not None:
res_list = []
for res_str in options.titrate_only.split(','):
try:
chain, resnum, inscode = parse_res_string(res_str)
except ValueError:
_LOGGER.critical(
'Invalid residue string: "{0:s}"'.format(res_str))
sys.exit(1)
res_list.append((chain, resnum, inscode))
options.titrate_only = res_list
# Set the no-print variable # Set the no-print variable
level = getattr(logging, options.log_level) level = getattr(logging, options.log_level)
_LOGGER.setLevel(level) _LOGGER.setLevel(level)

View File

@@ -157,7 +157,7 @@ class MolecularContainer:
conformation='AVR', reference=reference) conformation='AVR', reference=reference)
def get_folding_profile(self, conformation='AVR', reference="neutral", def get_folding_profile(self, conformation='AVR', reference="neutral",
grid=[0., 14., 0.1]): grid: Tuple[float, float, float] = (0., 14., 0.1)):
"""Get a folding profile. """Get a folding profile.
Args: Args:
@@ -174,25 +174,25 @@ class MolecularContainer:
4. stability_range 4. stability_range
""" """
# calculate stability profile # calculate stability profile
profile = [] profile: List[Tuple[float, float]] = []
for ph in make_grid(*grid): for ph in make_grid(*grid):
conf = self.conformations[conformation] conf = self.conformations[conformation]
ddg = conf.calculate_folding_energy(ph=ph, reference=reference) ddg = conf.calculate_folding_energy(ph=ph, reference=reference)
profile.append([ph, ddg]) profile.append((ph, ddg))
# find optimum # find optimum
opt = [None, 1e6] opt: Tuple[Optional[float], float] = (None, 1e6)
for point in profile: for point in profile:
opt = min(opt, point, key=lambda v: v[1]) opt = min(opt, point, key=lambda v: v[1])
# find values within 80 % of optimum # find values within 80 % of optimum
range_80pct = [None, None] range_80pct: Tuple[Optional[float], Optional[float]] = (None, None)
values_within_80pct = [p[0] for p in profile if p[1] < 0.8*opt[1]] values_within_80pct = [p[0] for p in profile if p[1] < 0.8*opt[1]]
if len(values_within_80pct) > 0: if len(values_within_80pct) > 0:
range_80pct = [min(values_within_80pct), max(values_within_80pct)] range_80pct = (min(values_within_80pct), max(values_within_80pct))
# find stability range # find stability range
stability_range = [None, None] stability_range: Tuple[Optional[float], Optional[float]] = (None, None)
stable_values = [p[0] for p in profile if p[1] < 0.0] stable_values = [p[0] for p in profile if p[1] < 0.0]
if len(stable_values) > 0: if len(stable_values) > 0:
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: str = 'AVR', grid=[0., 14., .1]): def get_charge_profile(self, conformation: str = 'AVR', grid=[0., 14., .1]):

View File

@@ -12,13 +12,29 @@ Output routines.
import logging import logging
from datetime import date from datetime import date
from decimal import Decimal from decimal import Decimal
from os import PathLike
from pathlib import Path
from typing import IO, AnyStr, List, NoReturn, Optional, Union, TYPE_CHECKING
import warnings
from .parameters import Parameters
from . import __version__ from . import __version__
if TYPE_CHECKING:
from .atom import Atom
from .conformation_container import ConformationContainer
from .molecular_container import MolecularContainer
# https://docs.python.org/3/glossary.html#term-path-like-object
_PathLikeTypes = (PathLike, str)
_PathArg = Union[PathLike, str]
_IOSource = Union[IO[AnyStr], PathLike, str]
_TextIOSource = _IOSource[str]
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
def open_file_for_writing(input_file): def open_file_for_writing(input_file: _TextIOSource) -> IO[str]:
"""Open file or file-like stream for writing. """Open file or file-like stream for writing.
TODO - convert this to a context manager. TODO - convert this to a context manager.
@@ -27,17 +43,13 @@ def open_file_for_writing(input_file):
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 to get file mode. then will attempt to get file mode.
""" """
try: if isinstance(input_file, _PathLikeTypes):
return open(input_file, 'wt')
if not input_file.writable(): if not input_file.writable():
raise IOError("File/stream not open for writing") raise IOError("File/stream not open for writing")
return input_file return input_file
except AttributeError:
pass
try:
file_ = open(input_file, 'wt')
except FileNotFoundError:
raise Exception('Could not open {0:s}'.format(input_file))
return file_
def write_file(filename, lines): def write_file(filename, lines):
@@ -47,6 +59,7 @@ def write_file(filename, lines):
filename: name of file filename: name of file
lines: lines to write to file lines: lines to write to file
""" """
warnings.warn("unused and untested by propka")
file_ = open_file_for_writing(filename) file_ = open_file_for_writing(filename)
for line in lines: for line in lines:
file_.write("{0:s}\n".format(line)) file_.write("{0:s}\n".format(line))
@@ -101,17 +114,22 @@ def write_pdb_for_protein(
pdbfile.close() pdbfile.close()
def write_pdb_for_conformation(conformation, filename): def write_pdb_for_conformation(conformation: "ConformationContainer",
filename: _PathArg):
"""Write PDB conformation to a file. """Write PDB conformation to a file.
Args: Args:
conformation: conformation container conformation: conformation container
filename: filename for output filename: filename for output
""" """
warnings.warn("unused and untested by propka")
write_pdb_for_atoms(conformation.atoms, filename) write_pdb_for_atoms(conformation.atoms, filename)
def write_pka(protein, parameters, filename=None, conformation='1A', def write_pka(protein: "MolecularContainer",
parameters: Parameters,
filename: Optional[_PathArg] = None,
conformation='1A',
reference="neutral", _="folding", verbose=False, reference="neutral", _="folding", verbose=False,
__=None): __=None):
"""Write the pKa-file based on the given protein. """Write the pKa-file based on the given protein.
@@ -129,8 +147,6 @@ def write_pka(protein, parameters, filename=None, conformation='1A',
verbose = True verbose = True
if filename is None: if filename is None:
filename = "{0:s}.pka".format(protein.name) filename = "{0:s}.pka".format(protein.name)
# TODO - this would be much better with a context manager
file_ = open(filename, 'w')
if verbose: if verbose:
_LOGGER.info("Writing {0:s}".format(filename)) _LOGGER.info("Writing {0:s}".format(filename))
# writing propka header # writing propka header
@@ -149,11 +165,10 @@ def write_pka(protein, parameters, filename=None, conformation='1A',
# printing Protein Charge Profile # printing Protein Charge Profile
str_ += get_charge_profile_section(protein, conformation=conformation) str_ += get_charge_profile_section(protein, conformation=conformation)
# now, writing the pka text to file # now, writing the pka text to file
file_.write(str_) Path(filename).write_text(str_, encoding="utf-8")
file_.close()
def print_tm_profile(protein, reference="neutral", window=[0., 14., 1.], def print_tm_profile(protein: NoReturn, reference="neutral", window=[0., 14., 1.],
__=[0., 0.], tms=None, ref=None, _=False, __=[0., 0.], tms=None, ref=None, _=False,
options=None): options=None):
"""Print Tm profile. """Print Tm profile.
@@ -186,7 +201,7 @@ def print_tm_profile(protein, reference="neutral", window=[0., 14., 1.],
_LOGGER.info(str_) _LOGGER.info(str_)
def print_result(protein, conformation, parameters): def print_result(protein: "MolecularContainer", conformation: str, parameters: Parameters):
"""Prints all resulting output from determinants and down. """Prints all resulting output from determinants and down.
Args: Args:
@@ -197,7 +212,7 @@ def print_result(protein, conformation, parameters):
print_pka_section(protein, conformation, parameters) print_pka_section(protein, conformation, parameters)
def print_pka_section(protein, conformation, parameters): def print_pka_section(protein: "MolecularContainer", conformation: str, parameters: Parameters):
"""Prints out pKa section of results. """Prints out pKa section of results.
Args: Args:
@@ -212,7 +227,7 @@ def print_pka_section(protein, conformation, parameters):
_LOGGER.info("pKa summary:\n%s", str_) _LOGGER.info("pKa summary:\n%s", str_)
def get_determinant_section(protein, conformation, parameters): def get_determinant_section(protein: "MolecularContainer", conformation: str, parameters: Parameters):
"""Returns string with determinant section of results. """Returns string with determinant section of results.
Args: Args:
@@ -244,7 +259,8 @@ def get_determinant_section(protein, conformation, parameters):
return str_ return str_
def get_summary_section(protein, conformation, parameters): def get_summary_section(protein: "MolecularContainer", conformation: str,
parameters: Parameters):
"""Returns string with summary section of the results. """Returns string with summary section of the results.
Args: Args:
@@ -266,7 +282,8 @@ def get_summary_section(protein, conformation, parameters):
def get_folding_profile_section( def get_folding_profile_section(
protein, conformation='AVR', direction="folding", reference="neutral", protein: "MolecularContainer",
conformation='AVR', direction="folding", reference="neutral",
window=[0., 14., 1.0], _=False, __=None): window=[0., 14., 1.0], _=False, __=None):
"""Returns string with the folding profile section of the results. """Returns string with the folding profile section of the results.
@@ -373,6 +390,7 @@ def write_scwrl_sequence_file(sequence, filename="x-ray.seq", _=None):
TODO - figure out what this is TODO - figure out what this is
""" """
warnings.warn("unused and untested by propka")
with open(filename, 'w') as file_: with open(filename, 'w') as file_:
start = 0 start = 0
while len(sequence[start:]) > 60: while len(sequence[start:]) > 60:
@@ -536,7 +554,7 @@ def make_interaction_map(name, list_, interaction):
return res return res
def write_pdb_for_atoms(atoms, filename, make_conect_section=False): def write_pdb_for_atoms(atoms: List["Atom"], filename: _PathArg, make_conect_section=False):
"""Write out PDB file for atoms. """Write out PDB file for atoms.
Args: Args:

View File

@@ -12,10 +12,12 @@ function. If similar functionality is desired from a Python script
""" """
import logging import logging
import sys import sys
from typing import IO, Iterable, Optional
from propka.lib import loadOptions from propka.lib import loadOptions
from propka.input import read_parameter_file, read_molecule_file from propka.input import read_parameter_file, read_molecule_file
from propka.parameters import Parameters from propka.parameters import Parameters
from propka.molecular_container import MolecularContainer from propka.molecular_container import MolecularContainer
from propka.output import _PathArg
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -44,7 +46,9 @@ def main(optargs=None):
my_molecule.write_pka() my_molecule.write_pka()
def single(filename: str, optargs: tuple = (), stream=None, def single(filename: _PathArg,
optargs: Iterable[str] = (),
stream: Optional[IO[str]] = None,
write_pka: bool = True): write_pka: bool = True):
"""Run a single PROPKA calculation using ``filename`` as input. """Run a single PROPKA calculation using ``filename`` as input.
@@ -105,6 +109,7 @@ def single(filename: str, optargs: tuple = (), stream=None,
.. versionchanged:: 3.4.0 .. versionchanged:: 3.4.0
Removed ability to write out PROPKA input files. Removed ability to write out PROPKA input files.
""" """
filename = str(filename)
# Deal with input optarg options # Deal with input optarg options
optargs = tuple(optargs) optargs = tuple(optargs)
optargs += (filename,) optargs += (filename,)

27
tests/test_lib.py Normal file
View File

@@ -0,0 +1,27 @@
import propka.lib as m
import argparse
import pytest
def test_parse_res_string():
assert m.parse_res_string("C:123") == ("C", 123, " ")
assert m.parse_res_string("C:123B") == ("C", 123, "B")
assert m.parse_res_string("ABC:123x") == ("ABC", 123, "x")
with pytest.raises(ValueError):
m.parse_res_string("C:B123")
with pytest.raises(ValueError):
m.parse_res_string("123B")
with pytest.raises(ValueError):
m.parse_res_string("C:123:B")
def test_parse_res_list():
assert m.parse_res_list("C:123") == [("C", 123, " ")]
assert m.parse_res_list("ABC:123,D:4,F:56X") == [
("ABC", 123, " "),
("D", 4, " "),
("F", 56, "X"),
]
with pytest.raises(argparse.ArgumentTypeError):
m.parse_res_list("C:B123")