From c53a724fc6c9c1cf323a204ceae9bb4aec2bf916 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren=20Wacker?= Date: Tue, 4 Aug 2026 22:17:41 +0200 Subject: [PATCH] encode the property URI where a dataset is exported, not only where it is provisioned Exporting any dataset on a profile with a space in a field name raised: rdflib refuses to serialize http://schema.org/Source Name at all. The earlier fix reached the model TTL's property definitions and the Sample Type pids, but the data RDF builds its predicates at a second site that still concatenated the name -- so SEEK held the encoded pid while the export could not be produced. The tests missed it because they asserted the two sides agree on the path that had been changed. Found by exporting a real dataset on cropxr-metabolomics against a running SEEK, which is what the fix was for. --- src/metaseed/seek/fairds.py | 6 +++- tests/test_seek/test_provision.py | 51 +++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/src/metaseed/seek/fairds.py b/src/metaseed/seek/fairds.py index b060aa0f..018c2a20 100644 --- a/src/metaseed/seek/fairds.py +++ b/src/metaseed/seek/fairds.py @@ -253,7 +253,11 @@ def walk(node: Any, parent_path: str) -> None: if key == "description": graph.add((uri, SCHEMA.description, Literal(value))) else: - graph.add((uri, SCHEMA[key], Literal(value))) + # Percent-encoded through the same helper provisioning uses: a + # field named with a space is what SEEK matches an imported + # sample to its attribute by, and rdflib refuses to serialize + # the unencoded form at all. + graph.add((uri, URIRef(property_uri(key)), Literal(value))) if key in entity_fields: used[key] = entity_fields[key] diff --git a/tests/test_seek/test_provision.py b/tests/test_seek/test_provision.py index bc78cee6..b3d61ac2 100644 --- a/tests/test_seek/test_provision.py +++ b/tests/test_seek/test_provision.py @@ -322,3 +322,54 @@ def test_the_data_rdf_uses_the_same_uri(self) -> None: emitted = {str(s) for s in graph.subjects()} assert property_uri("Source Name") in emitted assert URIRef("http://schema.org/Source Name") not in set(graph.subjects()) + + def test_a_dataset_with_a_spaced_field_name_exports(self) -> None: + """Exporting is where the unencoded URI actually bit. + + Provisioning was fixed first, and its tests passed, but the data RDF + built property URIs at a second site that still concatenated the field + name. rdflib refuses to serialize ``http://schema.org/Source Name`` at + all, so exporting any dataset on such a profile raised rather than + producing a graph SEEK could read. + """ + pytest.importorskip("rdflib") + from metaseed import MetaseedClient + from metaseed.seek.fairds import to_fair_data_station_rdf + from metaseed.seek.naming import property_uri + from metaseed.specs.schema import ( + EntityDefSpec, + FieldSpec, + FieldType, + ProfileSpec, + ) + + spec = ProfileSpec( + version="1.0", + name="spaced-export", + display_name="Spaced export", + description="d", + ontology="T", + root_entity="Investigation", + entities={ + "Investigation": EntityDefSpec( + description="d", + fields=[ + FieldSpec(name="title", type=FieldType.STRING), + FieldSpec(name="Source Name", type=FieldType.STRING), + ], + ) + }, + ) + + client = MetaseedClient.from_spec(spec.model_dump(mode="json")) + client.create_entity( + "Investigation", + {"title": "t", "Source Name": "S-1"}, + skip_validation=True, + ) + + rdf = to_fair_data_station_rdf(client) + text = rdf.decode() if isinstance(rdf, bytes) else rdf + + assert "schema.org/Source Name" not in text, "an unserializable URI" + assert property_uri("Source Name").rsplit("/", 1)[-1] in text