diff --git a/CHANGELOG b/CHANGELOG index 2d46f5b..cadd9b4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,10 @@ CHANGELOG ********* +Unreleased +========== +- Replaced the ``molecules`` argument with ``ligands`` in ``generate_lomap_network()``. Argument name ``molecules`` will be deprecated. + 2.3.0 ===== - Added shift option to MCS and cli (-s or --shift), this defaults to True. In combination with threed/-3 this option diff --git a/lomap/gufe_bindings/network_generation.py b/lomap/gufe_bindings/network_generation.py index f3db6b7..c9edc9c 100644 --- a/lomap/gufe_bindings/network_generation.py +++ b/lomap/gufe_bindings/network_generation.py @@ -12,14 +12,14 @@ from ..graphgen import GraphGen from .._due import due, Doi - +from ..utils import deprecated_kwargs logger = logging.getLogger(__name__) - +@deprecated_kwargs(name_mappings={'molecules':'ligands'}) @due.dcite(Doi("https://doi.org/10.1007/s10822-013-9678-y"), description="LOMAP") def generate_lomap_network( - molecules: list[gufe.SmallMoleculeComponent], + ligands: list[gufe.SmallMoleculeComponent], mappers: Union[AtomMapper, list[AtomMapper]], scorer: Callable, distance_cutoff: float = 0.4, @@ -35,7 +35,7 @@ def generate_lomap_network( Parameters ---------- - molecules : list[SmallMoleculeComponent] + ligands : list[SmallMoleculeComponent] molecules to map mappers : list[AtomMapper] or AtomMapper one or more Mapper functions to use to propose edges @@ -67,17 +67,17 @@ def generate_lomap_network( if isinstance(mappers, gufe.AtomMapper): mappers = [mappers] if actives is None: - actives = [False] * len(molecules) + actives = [False] * len(ligands) # gen n x n mappings with scores # initially all zero scores, i.e. impossible - mtx = np.zeros((len(molecules), len(molecules)), dtype=float) + mtx = np.zeros((len(ligands), len(ligands)), dtype=float) # np array of mappings mps = np.zeros_like(mtx, dtype=object) - # for all combinations of molecules - for i, j in itertools.combinations(range(len(molecules)), 2): - mA, mB = molecules[i], molecules[j] + # for all combinations of ligands + for i, j in itertools.combinations(range(len(ligands)), 2): + mA, mB = ligands[i], ligands[j] # pick best score across all mappings from all mappings best_mp: Optional[LigandAtomMapping] = None @@ -107,7 +107,7 @@ def generate_lomap_network( gg = GraphGen(score_matrix=mtx, ids=list(range(mtx.shape[0])), - names=[m.name for m in molecules], + names=[m.name for m in ligands], max_path_length=max_path_length, actives=actives, max_dist_from_active=max_dist_from_active, @@ -121,7 +121,7 @@ def generate_lomap_network( ln = LigandNetwork( edges=[mps[i, j] for i, j in n.edges], - nodes=molecules, + nodes=ligands, ) return ln diff --git a/lomap/tests/test_new_api.py b/lomap/tests/test_new_api.py index 68043da..a2f4550 100644 --- a/lomap/tests/test_new_api.py +++ b/lomap/tests/test_new_api.py @@ -29,10 +29,21 @@ def basic(): def test_generate_network_smoketest(basic): - network = lomap.generate_lomap_network( - molecules=basic, - mappers=lomap.LomapAtomMapper(), - scorer=lomap.default_lomap_score, - ) - - assert isinstance(network, gufe.LigandNetwork) + with pytest.deprecated_call(match="'molecules' is deprecated, please use 'ligands'"): + network = lomap.generate_lomap_network( + molecules=basic, + mappers=lomap.LomapAtomMapper(), + scorer=lomap.default_lomap_score, + ) + + assert isinstance(network, gufe.LigandNetwork) + + +def test_overdefined_deprecated_generate_network(basic): + with pytest.raises(ValueError, match="Both 'molecules' and 'ligands' are defined"): + lomap.generate_lomap_network( + molecules=basic, + ligands=basic, + mappers=lomap.LomapAtomMapper(), + scorer=lomap.default_lomap_score, + ) diff --git a/lomap/utils.py b/lomap/utils.py new file mode 100644 index 0000000..4f4f66a --- /dev/null +++ b/lomap/utils.py @@ -0,0 +1,46 @@ +import functools +import warnings +from typing import Any, Callable, Dict + + +def rename_kwargs( + func_name: str, kwargs: Dict[str, Any], name_mappings: Dict[str, str] +): + """Helper function for deprecating function arguments.""" + for old_name, new_name in name_mappings.items(): + deprecation_msg = ( + f"{func_name} argument '{old_name}' is deprecated, please use '{new_name}' instead.", + ) + if old_name in kwargs: + if new_name in kwargs: + raise ValueError( + f"Both '{old_name}' and '{new_name}' are defined for {func_name}." + + f"'{old_name}' is deprecated, please use '{new_name}' instead." + ) + + else: + warnings.warn(deprecation_msg, DeprecationWarning) + kwargs[new_name] = kwargs.pop(old_name) + return kwargs + + +def deprecated_kwargs(name_mappings: Dict[str, str]) -> Callable: + """Decorator for deprecating keyword arguments + + e.g. + @deprecated_kwarg({'old_arg':'new_arg'}) + def my_function(new_arg) + ... + """ + + def decorator(func: Callable) -> Callable: + @functools.wraps(func) + def wrapper(*args: Any, **kwargs: Any): + kwargs = rename_kwargs( + func_name=func.__name__, kwargs=kwargs, name_mappings=name_mappings + ) + return func(*args, **kwargs) + + return wrapper + + return decorator