RDF lists and language-tagged literals

TripleModel maps list[T] fields to ordered rdf:List structures and set[T] fields to multiple objects on one predicate.

list[T]rdf:List

Use a Python list when order matters and the graph should use an rdf:List head node (rdf:first / rdf:rest):

nick: list[str] = rdf_field("foaf:nick", default_factory=list)
from examples.doc._models import Person

person = Person(slug="alice", name="Alice", nick=["Al", "Alice"])
restored = Person.from_graph(person.to_graph(), person.subject_uri())
print(restored.nick)

Output:

['Al', 'Alice']

Sync modes clear the entire list structure when the field is None or [].

If the graph has more than one rdf:List head for the same predicate, import uses the first head only. Use on_duplicate="error" on import to surface ambiguous data; "ignore" suppresses the warning but does not merge lists.

set[T] → multiple objects

Use a set when you want several objects on the same predicate without an RDF list (order not guaranteed):

tag: set[str] = rdf_field("http://example.org/tag", default_factory=set)

See Multi-valued fields for sync and duplicate-object behaviour.

Language tags

LangString

from triplemodel.terms.lang import LangString

title: LangString = rdf_field("http://purl.org/dc/terms/title")
doc = Document(slug="d1", title=LangString("Hello", "en"))

Annotated[str, Lang("en")]

from typing import Annotated
from triplemodel.terms.lang import Lang

title: Annotated[str, Lang("en")] = rdf_field("http://purl.org/dc/terms/title")

Export emits Literal(..., lang=...); import restores the tag.

MultiLangString

When one predicate carries several language-tagged literals (for example rdfs:label@en and rdfs:label@fr), use MultiLangString instead of set[LangString]:

from triplemodel import MultiLangString, TripleModel, rdf_field
from triplemodel.vocab import RDFS

class Term(TripleModel):
    class Rdf:
        namespace = "http://example.org/terms/"
        id_field = "slug"

    slug: str
    label: MultiLangString = rdf_field(f"{RDFS}label")

term = Term(slug="t1", label=MultiLangString({"en": "Cat", "fr": "Chat"}))

Import collects every object on the predicate that has a language tag (untagged literals are skipped). Language tags are normalized to lowercase on import (for example EN and en map to the same key). Export writes one triple per map entry. Conflicting values for the same language respect on_duplicate on from_graph ("error", "warn", or keep the first).

Pydantic accepts a plain dict[str, str] for the field value. Prefer MultiLangString when you want one field for a language map; use set[LangString] only if you need an unordered bag of tags without a single keyed map API.

Helpers and MultiLangString

  • graph_value(graph, subject, field) on a MultiLangString field returns a MultiLangString instance.

  • objects_for_field returns a list[LangString] (one per language), not a MultiLangString.

  • graph_set does not accept a MultiLangString value; update the model field and use sync_to_graph or assign on the instance before export.