Skip to content

Commit d429c4e

Browse files
committed
fixup! Add type annotations to code base
1 parent 216a4e5 commit d429c4e

11 files changed

Lines changed: 403 additions & 230 deletions

File tree

src/biotite/application/application.py

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
# under the 3-Clause BSD License. Please see 'LICENSE.rst' for further
33
# information.
44

5+
from __future__ import annotations
6+
57
__name__ = "biotite.application"
68
__author__ = "Patrick Kunzmann"
79
__all__ = [
@@ -15,8 +17,13 @@
1517

1618
import abc
1719
import time
20+
from collections.abc import Callable
1821
from enum import Flag, auto
1922
from functools import wraps
23+
from typing import ParamSpec, TypeVar
24+
25+
_P = ParamSpec("_P")
26+
_R = TypeVar("_R")
2027

2128

2229
class AppState(Flag):
@@ -31,7 +38,9 @@ class AppState(Flag):
3138
CANCELLED = auto()
3239

3340

34-
def requires_state(app_state):
41+
def requires_state(
42+
app_state: AppState,
43+
) -> Callable[[Callable[_P, _R]], Callable[_P, _R]]:
3544
"""
3645
A decorator for methods of :class:`Application` subclasses that
3746
raises an :class:`AppStateError` in case the method is called, when
@@ -53,14 +62,16 @@ def requires_state(app_state):
5362
... pass
5463
"""
5564

56-
def decorator(func):
65+
def decorator(func: Callable[_P, _R]) -> Callable[_P, _R]:
5766
@wraps(func)
58-
def wrapper(*args, **kwargs):
67+
def wrapper(*args: _P.args, **kwargs: _P.kwargs) -> _R:
5968
# First parameter of method is always 'self'
6069
try:
6170
instance = args[0]
6271
except IndexError:
6372
raise TypeError("This method must be called from a class instance")
73+
if not isinstance(instance, Application):
74+
raise TypeError("This method must be an 'Application' method")
6475
if not instance._state & app_state:
6576
raise AppStateError(
6677
f"The application is in {instance.get_app_state()} state, "
@@ -115,21 +126,21 @@ class Application(metaclass=abc.ABCMeta):
115126
executed, while the application runs in the background.
116127
"""
117128

118-
def __init__(self):
119-
self._state = AppState.CREATED
129+
def __init__(self) -> None:
130+
self._state: AppState = AppState.CREATED
120131

121132
@requires_state(AppState.CREATED)
122-
def start(self):
133+
def start(self) -> None:
123134
"""
124135
Start the application run and set its state to *RUNNING*.
125136
This can only be done from the *CREATED* state.
126137
"""
127138
self.run()
128-
self._start_time = time.time()
139+
self._start_time: float = time.time()
129140
self._state = AppState.RUNNING
130141

131142
@requires_state(AppState.RUNNING | AppState.FINISHED)
132-
def join(self, timeout=None):
143+
def join(self, timeout: float | None = None) -> None:
133144
"""
134145
Conclude the application run and set its state to *JOINED*.
135146
This can only be done from the *RUNNING* or *FINISHED* state.
@@ -174,14 +185,14 @@ def join(self, timeout=None):
174185
self.clean_up()
175186

176187
@requires_state(AppState.RUNNING | AppState.FINISHED)
177-
def cancel(self):
188+
def cancel(self) -> None:
178189
"""
179190
Cancel the application when in *RUNNING* or *FINISHED* state.
180191
"""
181192
self._state = AppState.CANCELLED
182193
self.clean_up()
183194

184-
def get_app_state(self):
195+
def get_app_state(self) -> AppState:
185196
"""
186197
Get the current app state.
187198
@@ -196,7 +207,7 @@ def get_app_state(self):
196207
return self._state
197208

198209
@abc.abstractmethod
199-
def run(self):
210+
def run(self) -> None:
200211
"""
201212
Commence the application run. Called in :func:`start()`.
202213
@@ -205,7 +216,7 @@ def run(self):
205216
pass
206217

207218
@abc.abstractmethod
208-
def is_finished(self):
219+
def is_finished(self) -> bool:
209220
"""
210221
Check if the application has finished.
211222
@@ -219,7 +230,7 @@ def is_finished(self):
219230
pass
220231

221232
@abc.abstractmethod
222-
def wait_interval(self):
233+
def wait_interval(self) -> float:
223234
"""
224235
The time interval of :func:`is_finished()` calls in the joining
225236
process.
@@ -235,15 +246,15 @@ def wait_interval(self):
235246
pass
236247

237248
@abc.abstractmethod
238-
def evaluate(self):
249+
def evaluate(self) -> None:
239250
"""
240251
Evaluate application results. Called in :func:`join()`.
241252
242253
PROTECTED: Override when inheriting.
243254
"""
244255
pass
245256

246-
def clean_up(self):
257+
def clean_up(self) -> None:
247258
"""
248259
Do clean up work after the application terminates.
249260

src/biotite/application/autodock/app.py

Lines changed: 53 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,25 @@
22
# under the 3-Clause BSD License. Please see 'LICENSE.rst' for further
33
# information.
44

5+
from __future__ import annotations
6+
57
__name__ = "biotite.application.autodock"
68
__author__ = "Patrick Kunzmann"
79
__all__ = ["VinaApp"]
810

911
import copy
12+
from os import PathLike
1013
from tempfile import NamedTemporaryFile
14+
from typing import Any
1115
import numpy as np
1216
from biotite.application.application import AppState, requires_state
1317
from biotite.application.localapp import LocalApp, cleanup_tempfile
18+
from biotite.structure.atoms import AtomArray, AtomArrayStack
1419
from biotite.structure.connect import find_connected
1520
from biotite.structure.error import BadStructureError
1621
from biotite.structure.io.pdbqt import PDBQTFile
1722
from biotite.structure.residues import get_residue_masks, get_residue_starts_for
23+
from biotite.typing import XYZ, K, M, N, NDArray1, NDArray3
1824

1925

2026
class VinaApp(LocalApp):
@@ -24,7 +30,7 @@ class VinaApp(LocalApp):
2430
Parameters
2531
----------
2632
ligand : AtomArray
27-
The structure of the receptor molecule.
33+
The structure of the ligand molecule.
2834
Must have an associated :class:`BondList`.
2935
An associated ``charge`` annotation is recommended for proper
3036
calculation of partial charges.
@@ -63,25 +69,33 @@ class VinaApp(LocalApp):
6369
... )
6470
"""
6571

66-
def __init__(self, ligand, receptor, center, size, flexible=None, bin_path="vina"):
72+
def __init__(
73+
self,
74+
ligand: AtomArray[Any],
75+
receptor: AtomArray[N],
76+
center: NDArray1[XYZ, np.floating],
77+
size: NDArray1[XYZ, np.floating],
78+
flexible: NDArray1[N, np.bool_] | None = None,
79+
bin_path: PathLike[str] | str = "vina",
80+
) -> None:
6781
super().__init__(bin_path)
6882

6983
if ligand.bonds is None:
7084
raise ValueError("The ligand has no associated BondList")
7185
if receptor.bonds is None:
7286
raise ValueError("The receptor has no associated BondList")
7387

74-
self._ligand = ligand.copy()
75-
self._receptor = receptor.copy()
76-
self._center = copy.deepcopy(center)
77-
self._size = copy.deepcopy(size)
78-
self._is_flexible = flexible is not None
79-
self._seed = None
80-
self._exhaustiveness = None
81-
self._number = None
82-
self._energy_range = None
83-
84-
if self._is_flexible:
88+
self._ligand: AtomArray[Any] = ligand.copy()
89+
self._receptor: AtomArray[Any] = receptor.copy()
90+
self._center: NDArray1[Any, np.floating] = copy.deepcopy(center)
91+
self._size: NDArray1[Any, np.floating] = copy.deepcopy(size)
92+
self._is_flexible: bool = flexible is not None
93+
self._seed: int | None = None
94+
self._exhaustiveness: int | None = None
95+
self._number: int | None = None
96+
self._energy_range: float | None = None
97+
98+
if flexible is not None:
8599
flexible_indices = np.where(flexible)[0]
86100
self._flex_res_starts = np.unique(
87101
get_residue_starts_for(receptor, flexible_indices)
@@ -95,7 +109,7 @@ def __init__(self, ligand, receptor, center, size, flexible=None, bin_path="vina
95109
self._out_file = NamedTemporaryFile("r", suffix=".pdbqt", delete=False)
96110

97111
@requires_state(AppState.CREATED)
98-
def set_seed(self, seed):
112+
def set_seed(self, seed: int) -> None:
99113
"""
100114
Fix the seed for the random number generator to get
101115
reproducible results.
@@ -110,7 +124,7 @@ def set_seed(self, seed):
110124
self._seed = seed
111125

112126
@requires_state(AppState.CREATED)
113-
def set_exhaustiveness(self, exhaustiveness):
127+
def set_exhaustiveness(self, exhaustiveness: int) -> None:
114128
"""
115129
Set the *exhaustiveness* parameter for *Vina*.
116130
@@ -127,7 +141,7 @@ def set_exhaustiveness(self, exhaustiveness):
127141
self._exhaustiveness = exhaustiveness
128142

129143
@requires_state(AppState.CREATED)
130-
def set_max_number_of_models(self, number):
144+
def set_max_number_of_models(self, number: int) -> None:
131145
"""
132146
Set the maximum number of binding modes to generate.
133147
@@ -143,7 +157,7 @@ def set_max_number_of_models(self, number):
143157
self._number = number
144158

145159
@requires_state(AppState.CREATED)
146-
def set_energy_range(self, energy_range):
160+
def set_energy_range(self, energy_range: float) -> None:
147161
"""
148162
Set the maximum energy range of the generated models.
149163
@@ -158,7 +172,7 @@ def set_energy_range(self, energy_range):
158172
"""
159173
self._energy_range = energy_range
160174

161-
def run(self):
175+
def run(self) -> None:
162176
# Use different atom ID ranges for atoms in ligand and receptor
163177
# for unambiguous assignment, if the receptor contains flexible
164178
# residues
@@ -258,7 +272,7 @@ def run(self):
258272
self.set_arguments(arguments)
259273
super().run()
260274

261-
def evaluate(self):
275+
def evaluate(self) -> None:
262276
super().evaluate()
263277
out_file = PDBQTFile.read(self._out_file)
264278

@@ -276,15 +290,15 @@ def evaluate(self):
276290
[float(remark[12:].split()[0]) for remark in remarks]
277291
)
278292

279-
def clean_up(self):
293+
def clean_up(self) -> None:
280294
super().clean_up()
281295
cleanup_tempfile(self._ligand_file)
282296
cleanup_tempfile(self._receptor_file)
283297
cleanup_tempfile(self._receptor_flex_file)
284298
cleanup_tempfile(self._out_file)
285299

286300
@requires_state(AppState.JOINED)
287-
def get_energies(self):
301+
def get_energies(self) -> NDArray1[K, np.floating]:
288302
"""
289303
Get the predicted binding energy for each generated binding
290304
mode.
@@ -298,7 +312,7 @@ def get_energies(self):
298312
return self._energies
299313

300314
@requires_state(AppState.JOINED)
301-
def get_ligand_models(self):
315+
def get_ligand_models(self) -> AtomArrayStack[Any, Any]:
302316
"""
303317
Get the ligand structure with the conformations for each
304318
generated binding mode.
@@ -321,7 +335,7 @@ def get_ligand_models(self):
321335
return self._ligand_models
322336

323337
@requires_state(AppState.JOINED)
324-
def get_ligand_coord(self):
338+
def get_ligand_coord(self) -> NDArray3[M, N, XYZ, np.floating]:
325339
"""
326340
Get the ligand coordinates for each generated binding mode.
327341
@@ -342,7 +356,7 @@ def get_ligand_coord(self):
342356
return coord
343357

344358
@requires_state(AppState.JOINED)
345-
def get_flexible_residue_models(self):
359+
def get_flexible_residue_models(self) -> AtomArrayStack[Any, Any]:
346360
"""
347361
Get the structure for the flexible side chains with the
348362
conformations for each generated binding mode.
@@ -368,7 +382,7 @@ def get_flexible_residue_models(self):
368382
return self._flex_models
369383

370384
@requires_state(AppState.JOINED)
371-
def get_receptor_coord(self):
385+
def get_receptor_coord(self) -> NDArray3[M, N, XYZ, np.floating]:
372386
"""
373387
Get the get_receptor_coord coordinates for each generated
374388
binding mode.
@@ -401,7 +415,9 @@ def get_receptor_coord(self):
401415
coord[:, self._receptor_mask] = self._flex_models.coord
402416
return coord
403417

404-
def _get_flexible_residue(self, residue_start):
418+
def _get_flexible_residue(
419+
self, residue_start: int
420+
) -> tuple[np.ndarray, np.ndarray, int]:
405421
residue_indices = np.where(
406422
get_residue_masks(self._receptor, [residue_start])[0]
407423
)[0]
@@ -417,6 +433,8 @@ def _get_flexible_residue(self, residue_start):
417433

418434
# Find the index of the atom connected to root on the flexible
419435
# side chain (CB)
436+
if self._receptor.bonds is None:
437+
raise ValueError("The receptor has no associated BondList")
420438
root_connect_indices, _ = self._receptor.bonds.get_bonds(root_index)
421439
connected_index = None
422440
try:
@@ -450,7 +468,14 @@ def _get_flexible_residue(self, residue_start):
450468
return flex_mask, rigid_mask, root_index
451469

452470
@staticmethod
453-
def dock(ligand, receptor, center, size, flexible=None, bin_path="vina"):
471+
def dock(
472+
ligand: AtomArray[Any],
473+
receptor: AtomArray[N],
474+
center: NDArray1[XYZ, np.floating],
475+
size: NDArray1[XYZ, np.floating],
476+
flexible: NDArray1[N, np.bool_] | None = None,
477+
bin_path: PathLike[str] | str = "vina",
478+
) -> tuple[NDArray3[M, N, XYZ, np.floating], NDArray1[K, np.floating]]:
454479
"""
455480
Dock a ligand to a receptor molecule using *AutoDock Vina*.
456481
@@ -460,7 +485,7 @@ def dock(ligand, receptor, center, size, flexible=None, bin_path="vina"):
460485
Parameters
461486
----------
462487
ligand : AtomArray
463-
The structure of the receptor molecule.
488+
The structure of the ligand molecule.
464489
Must have an associated :class:`BondList`.
465490
An associated ``charge`` annotation is recommended for proper
466491
calculation of partial charges.

0 commit comments

Comments
 (0)