From 6db04f73f065ff302e741635993f9d803e56a9eb Mon Sep 17 00:00:00 2001 From: Andrew Davison Date: Sat, 22 Aug 2026 22:41:15 +0200 Subject: [PATCH] Return None from from_id() for a non-existent id on the untyped path --- fairgraph/kgobject.py | 2 ++ test/test_base.py | 27 +++++++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/fairgraph/kgobject.py b/fairgraph/kgobject.py index 17e8a933..0f761949 100644 --- a/fairgraph/kgobject.py +++ b/fairgraph/kgobject.py @@ -294,6 +294,8 @@ def from_id( if follow_links is not None: raise NotImplementedError data = client.instance_from_full_uri(uri, use_cache=use_cache, release_status=release_status) + if data is None: + return None type_ = data["@type"] if isinstance(type_, list): assert len(type_) == 1 diff --git a/test/test_base.py b/test/test_base.py index 3b10afed..704cc25c 100644 --- a/test/test_base.py +++ b/test/test_base.py @@ -14,6 +14,7 @@ from fairgraph.caching import generate_cache_key from fairgraph.errors import CannotBuildExistenceQuery from fairgraph.base import ErrorHandling +from .utils import mock_client import pytest @@ -669,3 +670,29 @@ def test_repr(self): uri = "https://kg.ebrains.eu/api/instances/00000000-0000-0000-0000-000000001234" proxy = KGProxy(MockKGObject, uri) assert repr(proxy) == 'KGProxy([MockKGObject], id="00000000-0000-0000-0000-000000001234")' + + +class TestFromId: + """KGObject.from_id() on the base class, where the type is not known up front.""" + + nonexistent = "https://kg.ebrains.eu/api/instances/11111111-2222-3333-4444-555555555555" + existing = "http://example.org/00000000-0000-0000-0000-000000000000" + + def test_returns_none_for_nonexistent_id(self, mock_client, mocker): + # Regression test for #115. instance_from_full_uri() returns None for an instance + # that does not exist or is not accessible. The typed path (from_uri) handles that + # and returns None, as from_id's docstring promises, but the untyped path indexed + # into the result regardless and raised + # "TypeError: 'NoneType' object is not subscriptable". + mocker.patch.object(mock_client, "instance_from_full_uri", return_value=None) + + assert KGObject.from_id(self.nonexistent, mock_client, release_status="any") is None + + def test_still_resolves_an_existing_id(self, mock_client): + # Companion to the above: the None guard must not swallow the success path. + # The mock client returns a Model instance for this URI, so from_id should look + # the type up in the registry and build the corresponding class. + obj = KGObject.from_id(self.existing, mock_client, release_status="any") + + assert obj is not None + assert obj.__class__.__name__ == "Model"