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

Added validation guardrail for AlchemicalNetworks with self-Transformations #189

Merged
merged 3 commits into from
Oct 6, 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
9 changes: 8 additions & 1 deletion alchemiscale/interface/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,14 @@ def create_network(
validate_scopes(scope, token)

an = AlchemicalNetwork.from_dict(network)
an_sk = n4js.create_network(network=an, scope=scope)

try:
an_sk = n4js.create_network(network=an, scope=scope)
except ValueError as e:
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail=e.args[0],
)

# create taskhub for this network
n4js.create_taskhub(an_sk)
Expand Down
3 changes: 3 additions & 0 deletions alchemiscale/interface/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from ..storage.models import Task, ProtocolDAGResultRef, TaskStatusEnum
from ..strategies import Strategy
from ..security.models import CredentialedUserIdentity
from ..validators import validate_network_nonself


class AlchemiscaleClientError(AlchemiscaleBaseClientError):
Expand Down Expand Up @@ -116,6 +117,8 @@ def create_network(
f"`scope` '{scope}' contains wildcards ('*'); `scope` must be *specific*"
)

validate_network_nonself(network)

from rich.progress import Progress

sk = self.get_scoped_key(network, scope)
Expand Down
2 changes: 2 additions & 0 deletions alchemiscale/storage/statestore.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@

from ..security.models import CredentialedEntity
from ..settings import Neo4jStoreSettings, get_neo4jstore_settings
from ..validators import validate_network_nonself


@lru_cache()
Expand Down Expand Up @@ -610,6 +611,7 @@ def create_network(self, network: AlchemicalNetwork, scope: Scope):
some of its components already exist in the database.

"""
validate_network_nonself(network)

ndict = network.to_shallow_dict()

Expand Down
64 changes: 64 additions & 0 deletions alchemiscale/tests/unit/test_validators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import pytest

from openfe_benchmarks import tyk2
from gufe import ChemicalSystem, Transformation, AlchemicalNetwork
from gufe.tests.test_protocol import DummyProtocol, BrokenProtocol

from alchemiscale import validators


@pytest.fixture(scope="session")
def network_self_transformation():
tyk2s = tyk2.get_system()
ligand = tyk2s.ligand_components[0]

cs = ChemicalSystem(
components={"ligand": ligand, "solvent": tyk2s.solvent_component},
name=f"{ligand.name}_water",
)

tf = Transformation(
stateA=cs,
stateB=cs,
protocol=DummyProtocol(settings=DummyProtocol.default_settings()),
name=f"{ligand.name}->{ligand.name}_water",
)

return AlchemicalNetwork(edges=[tf], name="self_transformation")


@pytest.fixture(scope="session")
def network_nonself_transformation():
tyk2s = tyk2.get_system()
ligand = tyk2s.ligand_components[0]
ligand2 = tyk2s.ligand_components[1]

cs = ChemicalSystem(
components={"ligand": ligand, "solvent": tyk2s.solvent_component},
name=f"{ligand.name}_water",
)

cs2 = ChemicalSystem(
components={"ligand": ligand2, "solvent": tyk2s.solvent_component},
name=f"{ligand2.name}_water",
)

tf = Transformation(
stateA=cs,
stateB=cs2,
protocol=DummyProtocol(settings=DummyProtocol.default_settings()),
name=f"{ligand.name}->{ligand2.name}_water",
)

return AlchemicalNetwork(edges=[tf], name="nonself_transformation")


def test_validate_network_nonself(
network_self_transformation, network_nonself_transformation
):
with pytest.raises(ValueError, match="uses the same `ChemicalSystem`"):
validators.validate_network_nonself(network_self_transformation)

out = validators.validate_network_nonself(network_nonself_transformation)

assert out is None
22 changes: 22 additions & 0 deletions alchemiscale/validators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"""
:mod:`alchemiscale.validators` --- validation guardrails for user input
=======================================================================

"""

from gufe import AlchemicalNetwork, Transformation


def validate_network_nonself(network: AlchemicalNetwork):
"""Check that the given AlchemicalNetwork features no Transformations with
the same ChemicalSystem for its two states.

A ``ValueError`` is raised if a `Transformation` is detected.

"""
for transformation in network.edges:
if transformation.stateA == transformation.stateB:
raise ValueError(
f"`Transformation` '{transformation.key}' uses the same `ChemicalSystem` '{transformation.stateA.key}' for both states; "
"this is currently not supported in `alchemiscale`"
)
Loading