From 43474acbaa54548b8815b3e52cfcb15278731cad Mon Sep 17 00:00:00 2001 From: David Dotson Date: Wed, 6 Nov 2024 20:48:50 -0700 Subject: [PATCH 1/3] Closes #297 --- alchemiscale/interface/api.py | 10 +++++++- alchemiscale/interface/client.py | 2 +- alchemiscale/models.py | 6 ++++- .../interface/client/test_client.py | 25 +++++++++++++++++++ 4 files changed, 40 insertions(+), 3 deletions(-) diff --git a/alchemiscale/interface/api.py b/alchemiscale/interface/api.py index 0784ea49..0ad4fe6c 100644 --- a/alchemiscale/interface/api.py +++ b/alchemiscale/interface/api.py @@ -96,7 +96,15 @@ def check_existence( n4js: Neo4jStore = Depends(get_n4js_depends), token: TokenData = Depends(get_token_data_depends), ): - sk = ScopedKey.from_str(scoped_key) + try: + sk = ScopedKey.from_str(scoped_key) + except ValueError as e: + raise HTTPException( + status_code=http_status.HTTP_422_UNPROCESSABLE_ENTITY, + detail=e.args[0], + ) + + validate_scopes(sk.scope, token) return n4js.check_existence(scoped_key=sk) diff --git a/alchemiscale/interface/client.py b/alchemiscale/interface/client.py index 2699ce4b..be9414b2 100644 --- a/alchemiscale/interface/client.py +++ b/alchemiscale/interface/client.py @@ -88,7 +88,7 @@ def get_scoped_key(self, obj: GufeTokenizable, scope: Scope) -> ScopedKey: "Scope for a ScopedKey must be specific; it cannot contain wildcards." ) - def check_exists(self, scoped_key: ScopedKey) -> bool: + def check_exists(self, scoped_key: Union[ScopedKey, str]) -> bool: """Returns ``True`` if the given ScopedKey represents an object in the database.""" return self._get_resource(f"/exists/{scoped_key}") diff --git a/alchemiscale/models.py b/alchemiscale/models.py index 75edd1d0..69fc1288 100644 --- a/alchemiscale/models.py +++ b/alchemiscale/models.py @@ -151,7 +151,11 @@ def __eq__(self, other): @classmethod def from_str(cls, string): - prefix, token, org, campaign, project = string.split("-") + try: + prefix, token, org, campaign, project = string.split("-") + except ValueError: + raise ValueError("input does not appear to be a `ScopedKey`") + gufe_key = GufeKey(f"{prefix}-{token}") return cls(gufe_key=gufe_key, org=org, campaign=campaign, project=project) diff --git a/alchemiscale/tests/integration/interface/client/test_client.py b/alchemiscale/tests/integration/interface/client/test_client.py index ceb968f4..dec0bfdf 100644 --- a/alchemiscale/tests/integration/interface/client/test_client.py +++ b/alchemiscale/tests/integration/interface/client/test_client.py @@ -86,10 +86,35 @@ def test_create_network( network_sks = user_client.query_networks() assert an_sk in network_sks + assert user_client.check_exists(an_sk) + # TODO: make a network in a scope that doesn't have any components in # common with an existing network # user_client.create_network( + def test_check_exists( + self, + scope_test, + n4js_preloaded, + user_client: client.AlchemiscaleClient, + network_tyk2, + ): + an_sks = user_client.query_networks() + + # check that a known existing AlchemicalNetwork exists + assert user_client.check_exists(an_sks[0]) + + # check that an AlchemicalNetwork that doesn't exist shows as not existing + an_sk = an_sks[0] + an_sk_nonexistent = ScopedKey(gufe_key=GufeKey('AlchemicalNetwork-lol'), **scope_test.dict()) + assert not user_client.check_exists(an_sk_nonexistent) + + # check that we get an exception when we try a malformed key + with pytest.raises(AlchemiscaleClientError, + match="Status Code 422 : Unprocessable Entity : input does not appear to be a `ScopedKey`", + ): + user_client.check_exists('lol') + @pytest.mark.parametrize(("state",), [[state.value] for state in NetworkStateEnum]) def test_set_network_state( self, From df0e355dc08f9d330eb0cc2e90e085c2c76916a6 Mon Sep 17 00:00:00 2001 From: David Dotson Date: Wed, 6 Nov 2024 20:50:51 -0700 Subject: [PATCH 2/3] Black! --- alchemiscale/interface/api.py | 1 - .../tests/integration/interface/client/test_client.py | 11 +++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/alchemiscale/interface/api.py b/alchemiscale/interface/api.py index 0ad4fe6c..fef0b2c3 100644 --- a/alchemiscale/interface/api.py +++ b/alchemiscale/interface/api.py @@ -104,7 +104,6 @@ def check_existence( detail=e.args[0], ) - validate_scopes(sk.scope, token) return n4js.check_existence(scoped_key=sk) diff --git a/alchemiscale/tests/integration/interface/client/test_client.py b/alchemiscale/tests/integration/interface/client/test_client.py index dec0bfdf..7146c50f 100644 --- a/alchemiscale/tests/integration/interface/client/test_client.py +++ b/alchemiscale/tests/integration/interface/client/test_client.py @@ -106,14 +106,17 @@ def test_check_exists( # check that an AlchemicalNetwork that doesn't exist shows as not existing an_sk = an_sks[0] - an_sk_nonexistent = ScopedKey(gufe_key=GufeKey('AlchemicalNetwork-lol'), **scope_test.dict()) + an_sk_nonexistent = ScopedKey( + gufe_key=GufeKey("AlchemicalNetwork-lol"), **scope_test.dict() + ) assert not user_client.check_exists(an_sk_nonexistent) # check that we get an exception when we try a malformed key - with pytest.raises(AlchemiscaleClientError, + with pytest.raises( + AlchemiscaleClientError, match="Status Code 422 : Unprocessable Entity : input does not appear to be a `ScopedKey`", - ): - user_client.check_exists('lol') + ): + user_client.check_exists("lol") @pytest.mark.parametrize(("state",), [[state.value] for state in NetworkStateEnum]) def test_set_network_state( From c1b9fadbc64a5daaa18b264fa29d1d88dc74ab2b Mon Sep 17 00:00:00 2001 From: "David L. Dotson" Date: Mon, 11 Nov 2024 16:20:33 -0700 Subject: [PATCH 3/3] Update alchemiscale/interface/client.py @ianmkenney suggestion Co-authored-by: Ian Kenney --- alchemiscale/interface/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/alchemiscale/interface/client.py b/alchemiscale/interface/client.py index be9414b2..eaba27b7 100644 --- a/alchemiscale/interface/client.py +++ b/alchemiscale/interface/client.py @@ -88,7 +88,7 @@ def get_scoped_key(self, obj: GufeTokenizable, scope: Scope) -> ScopedKey: "Scope for a ScopedKey must be specific; it cannot contain wildcards." ) - def check_exists(self, scoped_key: Union[ScopedKey, str]) -> bool: + def check_exists(self, scoped_key: ScopedKey | str) -> bool: """Returns ``True`` if the given ScopedKey represents an object in the database.""" return self._get_resource(f"/exists/{scoped_key}")