Changelog
Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased]
[0.13.1] - 2026-06-01
Fixed
Polymorphic query hydration —
.polymorphic()SELECT results typed as registered subtypes hydrate correctly (previously skipped whenrdf:typedid not exactly match the query root class)not_()compiler — negation works for property-path (property_eq) and IRI string filters (FieldRef.str()/lower()/upper())Identity map — embedded models inside relationship collections are registered on
put/get/merge(parity with cascade graph walks)depth_satisfied— models with empty multi-ref fields no longer fail depth-1 cache checks indefinitelyHttpStore/AsyncHttpStoresession cache —sync_mirror()andpull_subjects_into_mirror()bumpmirror_generation, clearing session identity/hydration caches on the next readCONSTRUCT pull — response body parsed using
Content-Type(not always Turtle)Fuseki integration tests — admin/update helpers use the same default Basic auth as
HttpStorewhenFUSEKI_ADMIN_PASSWORDis unset
[0.13.0] - 2026-05-22
Fixed
Multi-ref cascade —
putonset[ResourceRef]no longer deletes all linked targets when one ref is removed; remainingResourceRefvalues are protected during orphan cleanup (same asset[IRI])
Added
triplemodel>=0.12.0,<2— multi-valued mapping,MultiLangString,TypedLiteral,ResourceRef,OntologyRegistry,BackPopulates(re-exported fromsparqlmodel)Multi-valued export/load/cascade —
set/listscalars and relationship refs; orphan cleanup for removed collection membersSchemaRegistry— alias forOntologyRegistry;Rdf.ontology_registry;Query.polymorphic()Query DSL —
Query.values(...),not_()/~expr,FieldRef.str()/lower()/upper(),property_eq()/property_path()for SPARQL property pathsCollection filters —
.in_()on multi-valued scalar and ref fieldsRelationship—inverse=,back_populates=,lang=onField,literal_datatype=,transitive=
Changed
Field classification uses TripleModel cardinality (
is_relationship_field, collection-aware cascade)
Documentation
ROADMAP — 0.13 shipped; focus 0.14; SPECS P1 modeling items checked
Guides — models, queries, ORM, README, ECOSYSTEM (
triplemodel>=0.12.0)
[0.12.0] - 2026-05-22
Added
HttpStore/AsyncHttpStoregraph_store_url— Graph Store HTTP endpoint for full-graph mirror syncsync_mirror()— replace the local mirror from remote via GSP GET (requiresgraph_store_url)http_common.default_graph_store_url()— Fuseki heuristic (.../sparql→.../data)Fuseki integration tests —
tests/test_http_store_integration.py(CI runs againststain/jena-fuseki)
Fixed
Fuseki integration tests — separate
DELETE WHEREandINSERT DATAwith;in combined SPARQL Update requestsGET SELECT — preserve existing query parameters on
read_endpoint/sparql_url(Fusekidefault-graph-uri, etc.)CONSTRUCT pull — expand compact IRIs in
VALUESusing storeprefixes=(aligned with mirror removal)Session
get/ pull — auto-pull and cache checks userdf:type(not merely any triple on the subject)merge/add— register embedded models in the identity map;addclears pendingput(..., flush=False)for the same subjectHTTP retries — close retryable responses before backoff (sync
close, asyncaclose);is_select_queryignores/* */block comments
Documentation
PRODUCTION — mirror sync (
sync_mirror), Fuseki URL table; Production HttpStore milestone completeROADMAP — phase 0.12 shipped; focus 0.13 (modeling)
SPECS — P0 HttpStore
sync_mirrorcheckedMakefile —
fuseki-up/fuseki-downfor local integration parity with CI
[0.11.0] - 2026-05-22
Added
HTTP resilience on
HttpStore/AsyncHttpStore— shared retry transport inhttp_common(max_retries,retry_backoff; retries 502/503/504 and connection/timeouts)Batched remote UPDATE —
max_triples_per_update(default 500) chunksINSERT DATA/DELETE DATA; local mirror updates only after all remote chunks succeedquery_method— optionalGETvsPOSTfor remote SELECT (QueryMethodexported fromsparqlmodel.stores)
Changed
Remote SELECT — default remains POST; GET uses SPARQL Protocol query string (
?query=)CONSTRUCT pull — uses the same retry policy as SELECT and UPDATE
Documentation
PRODUCTION, troubleshooting, SPECS, FastAPI guide — HTTP resilience defaults, batching, partial-failure semantics, GET URL limits
ROADMAP — phase 0.11 shipped; current focus 0.12
[0.10.0] - 2026-05-22
Added
HttpStore/AsyncHttpStoremirror_mode—writer(default) orremote_authoritativefor per-store mirror sync policyReplace-on-pull —
pull_subjects_into_mirrorremoves existing mirror triples per IRI before merging CONSTRUCT results (empty remote response clears the subject in the mirror)
Changed
get/refreshwithmirror_mode="remote_authoritative"— always CONSTRUCT-pull before hydrate; evicts identity/hydration cache for that readget/refreshwith defaultwriter— pull only when the subject is missing from the mirror (unchanged 0.9.x gating)
Fixed
Identity map — deep
get/refreshreload expires cascade subjects before merging loaded state; composed models registered onput/get/refreshso nestedget(Model, iri)matches relationship instancesmerge/expunge— drop pendingput(..., flush=False)for the same subject asexpire(single-subjectexpungeonly;expunge_allunchanged)remove_pending_for— no longer callsensure_id()on queued models while filtering the pending queueis_select_query— accepts inlinePREFIX/BASE,#line comments, andSELECT DISTINCTpull_subjects_into_mirror— malformed CONSTRUCT Turtle responses raiseQueryError(sync and async HttpStore)
Documentation
PRODUCTION, troubleshooting, SPECS, sessions guide — mirror modes and query vs
getauthorityROADMAP — phase 0.10 shipped; next focus 0.11 (HTTP resilience)
AsyncHttpStoreclass docstring aligned withHttpStore(mirror modes, auth, single-writer)docs/PLAN.md— TripleModel dependency>=0.10docs/SPECS.md— closed-store behavior includespull_subjects_into_mirror
[0.9.2] - 2026-05-21
Fixed
refreshon HttpStore / AsyncHttpStore — auto-pulls remote subjects into the mirror before reload (parity withget)merge— partial detached instances no longer clear unset relationship fields; hydration cache invalidated after mergeHydration —
Relationship | IRIfields keep IRI object references when the target has nordf:typein the graph
Changed
CI — smoke-run
examples/realworld/scripts on Python 3.12 (pip install -e ".[http]"sohttpxis available at import time)
Documentation
README — dev install note for
pytest-asyncio/uv sync --extra devSessions guide and troubleshooting —
refreshmirror pull on HTTP stores (0.9.2+)
[0.9.1] - 2026-05-21
Added
HttpStore/AsyncHttpStore— optionalread_endpoint/write_endpoint(Fuseki-style split URLs)pull_subjects_into_mirroron HTTP stores;getauto-pulls remote subject triples into the mirror when missingSPARQL JSON parsing —
pyoxigraph.parse_query_resultsfor remote SELECT bindings
Fixed
Hydration cache —
get/refreshrespectdepth; shallowrefreshno longer leaves stale deep hydration entriesdepth_satisfied— aligneddepth=1with per-field checks; requires at least one loaded relationship hop whendepth > 0Compiler —
!BOUNDguards for<,>,<=,>=on optional relationship paths (consistent with!=)
Changed
HttpStore.query/AsyncHttpStore.query— reject non-SELECT SPARQL; ASK JSON responses raiseQueryErrorInjected
httpxclients honor the storetimeoutwhen provided
Documentation
Troubleshooting — mirror sync target 1.0; SPECS TripleModel dependency
>=0.10PRODUCTION, README — partial HttpStore mirror pull (0.9.1)
[0.9.0] - 2026-05-21
Added
SPARQLSession.merge/refresh/expunge/expunge_all— SQLAlchemy-style identity map control (sync and async)AsyncSPARQLSession— same cache-control methods
Changed
Field updates on
mergeandrefreshuse Pydanticmodel_validatewhen copying graph state onto cached instances
Documentation
Sessions guide — cache control flows and object states
ORM, PRODUCTION, FastAPI — scoped session pattern, threading, lifecycle tables
SPECS P1 session items checked; ROADMAP 0.9 shipped
[0.8.1] - 2026-05-21
Fixed
put(flush=True)— clears pending subjects for the model cascade whenautoflush=False(sync and async)Identity map —
putinvalidates hydration cache using pre-write cascade subjects (orphaned embeds evicted correctly)order_by— nullable relationship hops useOPTIONALfor sort bindings (resources without a link are not dropped)IRI relationship refs — filters on
Relationship | IRIfields match IRI-only edges without requiringrdf:typeon the join variable!=with.use_inequality_for_ne()— on nullable optional paths, includes!BOUNDso missing links match like default!=
Changed
FieldRef.is_(None)/is_not(None)— nested relationship paths compile (scalar leaf still requires a relationship field)parse_count_bindings— invalid or negative COUNT values raiseQueryErrorlimit/offset— reject negative values (including-0)
Documentation
Query guide —
order_byon nullable hops, IRI reference filters, inequality vs default!=PRODUCTION — async FastAPI availability corrected to 0.6+
README — known limitations for query/session semantics
[0.8.0] - 2026-05-21
Added
Query.offset/AsyncQuery.offset—OFFSETin compiled SELECTQuery.order_by/AsyncQuery.order_by—ORDER BYon bound scalar fields (desc=Truefor descending)Query.count/AsyncQuery.count—COUNT(DISTINCT ?root)without pagination clausesFieldRef.is_(None)/is_not(None)— absence or presence of nullableRelationshiplinksCompiler —
OPTIONALblocks for nullable relationship hops;!=on optional paths includes resources with no link (!BOUNDdisjunct)
Changed
first()— ignores.offset()as well as.limit()(always first match in filter/sort order withLIMIT 1)
Documentation
Query guide, SPECS P0 checklist, README, PRODUCTION, ORM, ROADMAP — 0.8 query lists shipped
[0.7.1] - 2026-05-21
Added
SPARQLSession.from_rdf_file— open an in-memory session from an on-disk RDF file (Turtle, TriG, etc.)examples/realworld/— Nobel, DCAT, Wikidata, and Schema.org sample scripts with bundled.ttldataDocs —
guides/realworldwithliteralincludefrom example sources; linked from site index and README
Documentation
Sphinx/CI doc builds:
make docs,PYTHONWARNINGSfor intersphinx, updated Pydantic intersphinx URL, relative links for example data filesPyPI link in installation guide (removed broken
#historyanchor)
[0.7.0] - 2026-05-20
Changed
serializers— file I/O delegates format resolution to TripleModelinfer_format; removedSUPPORTED_FORMATSand local alias tablesexport_model— usesSPARQLModel.serialize()(TripleModel) instead ofmodel_to_graph+dump_graphFastAPI —
negotiated_responseresolvesAcceptviainfer_format; models serialize directly for HTTP bodies
Fixed
FieldRef.in_()— reject barestrvalues (previously split into characters); use("value",)or["value"]for a single memberQuery DSL —
(compare) & (or_group)now raises the same clearQueryErroras(or_group) & (compare)instead of a compiler flatten error
Documentation
JSON-LD:
model_dump_jsonld()/model_validate_jsonld()remain ORM dict helpers (cascade-aware); file/HTTP JSON-LD usesserialize(format="json-ld")orexport_modelREADME, ORM guide, and SPECS updated for delegated file I/O; prefer
Model.parse/Model.serializefor filesSPECS, README, query guide —
first()ignores prior.limit();in_()string caveatPRODUCTION, troubleshooting — do not mutate
session.graphonHttpStore; reverse AND/OR precedence exampleFastAPI guide —
negotiated_response(..., formats=)keys-only behavior
Tests
Async query/compiler parity suite (
tests/test_async_compiler_parity.py)Unique test
rdf_typeURIs andpytest.warnsfor intentional warnings (fewer noisy pytest warnings)
Notes
sparqlmodel.serializers(export_graph,import_graph,export_model) remains for backward compatibility — thin wrappers onlyUnsupported format errors now come from TripleModel (
Unknown RDF format)
[0.6.0] - 2026-05-20
Added
AsyncSPARQLSession—async with,await put/get/delete/add,flush,rollback_pending,execute,query→AsyncQuerywithawait .all()/.first()(same expression DSL as sync)AsyncMemoryStore,AsyncHttpStore—httpx.AsyncClientmirror semantics matching syncHttpStore; shared helpers instores/http_common.pysession_core— shared CRUD/hydration for sync and async sessions (sync API unchanged)FastAPI —
init_async_app,get_async_session,AsyncSessionDep,async_http_store_lifespan,async_session_dependencypytest-asyncioin dev extras;asyncio_mode = autofor tests
Fixed
compile_where/compile_compare— default!=compilation usesNOT EXISTS(matchesQueryand 0.5.2 docs); passuse_not_exists_for_ne=Falsefor inequality modeget()identity map — shallowdepth=0reload updates identity; graph miss evicts stale identity/hydration without dropping pendingputqueue (sync and async)get()cache — hydration and identity entries invalidated when the backing graph no longer contains the subject (including directstore.update_graphbypassing the session)hydrate_bindings— autoflush pendingputbefore hydrating (sync and async)Session context manager —
__exit__/__aexit__preserve flush errors instead of masking them with pending-closeRuntimeErrorSPARQL IRI safety — validate predicate/type IRIs and
term_to_n3NamedNode values; reject"and control characters in IRI tokens; validate RDF language tags; escape additional control characters in literalsQuery compiler — reject non-finite
floatfilter valuesclose()— mark session closed only after store teardown succeeds;AsyncSPARQLSession.closeskipsaclosewhen the store has noaclose(sync parity)FastAPI —
session_dependency/async_session_dependencyno longer close sharedinit_app/http_store_lifespanstores whenclose_on_exit=True
Documentation
Troubleshooting, SPECS, README, and query guide — OPTIONAL/pagination milestones and
==operator description aligned with 0.6/0.7 roadmapHttpStore
query().all()mirror behavior; FastAPIsparql_storelifecycle notes
Notes
Async HTTP uses existing
sparqlmodel[http](httpx); no separate[async]extraSync
SPARQLSession,HttpStore, andSessionDepare unchangedaio-rdfRust async I/O remains out of scope (seedocs/ASYNC_RDF_RUST_PLAN.md)
[0.5.2] - 2026-05-20
Fixed
delete()— drops pendingput(..., flush=False)for the root and cascade subjects (no resurrection onflush())Hydration cache —
get()no longer caches permanent “not found”; IRI-wide invalidation onput/add/deleteclears cross-type stale misses!=query default — usesFILTER NOT EXISTS(correct for multi-valued predicates; includes resources with no value for the field)rdf_n3— literal escaping aligned with the query compiler (\n,\r,\tin HttpStore UPDATE bodies)load_from_graph— non-cascade relationships with embedded models raiseHydrationErrorinstead of passing nested instances into validationuse_optional_for_comparisons(False)— restores default NOT EXISTS mode without overriding a separate.use_not_exists_for_ne()choice
Changed
Breaking (query): default
Person.field != valuematches former.use_not_exists_for_ne(); use.use_inequality_for_ne()for pre-0.5.2 inequality semantics (excludes unbound values; incorrect for multi-valued predicates)
Added
Query.use_inequality_for_ne()— opt-in legacy!=compilation (pattern +FILTER(?v != obj))
[0.5.1] - 2026-05-19
Fixed
Query DSL —
((A | B) & C)raisesQueryErrorinstead of silently compiling asA ∧ B ∧ C; use.where((A | B), C)for OR combined with ANDSession hydration cache —
get(..., depth=2)checks all relationship branches before reusing identity (multi-relationship models)use_optional_for_comparisons(False)— restores default!=semantics after enabling NOT EXISTS mode
Documentation
Query guide — OR/AND composition,
first()vslimit(),use_optional_for_comparisonsnaming; pagination milestone aligned to 0.7–0.8Troubleshooting — OR/AND
QueryError, session__exit__with pending queue androllback_on_error=FalseSphinx — nitpicky cross-ref checking; docs build clean with
-W
[0.5.0] - 2026-05-18
Added
Pyoxigraph engine —
triplemodel>=0.10.0,<2,pyoxigraph>=0.5,<0.6; session graphs aretriplemodel.Storesparqlmodel.rdf_n3— N3 formatting for compiler filters and HTTP UPDATE bodiessparqlmodel.stores.sparql_json— SPARQL Results JSON parser (no rdflib)
Changed
Breaking:
Store.graphandSPARQLSession.graphreturntriplemodel.Store, notrdflib.GraphBreaking:
Store.update_graphacceptsStoredeltas; custom store implementations must migrateMemoryStore/HttpStore— pyoxigraph-backed mirror; SELECT bindings use term.valuestringsserializers—export_graph/import_graphdelegate to TripleModeldump_graph/load_graphcompiler— literal/IRI formatting viardf_n3(not RDFLib.n3())FastAPI — Turtle/JSON-LD responses via
export_graphROADMAP — async ORM milestone renumbered to 0.6; file I/O delegation 0.7
Removed
rdflib as a required dependency
Fixed
Hydration DAGs —
load_from_graphno longer treats shared embedded resources as cyclesSession identity — canonical identity per IRI with per-depth hydration cache
Query compiler — absolute
urn:/http(s)://strings on IRI-typed fields compile as IRIs; escaped newlines in string literalsSession — pending
putinvalidates cascade caches; dedupes pending queue;__exit__exception handlingQuery.use_optional_for_comparisons(),OrExprchaining,FieldRef.in_sequences,Query.first()limit behavior
[0.4.0] - 2026-05-18
Added
Unified model (Option A) —
SPARQLModelsubclassesTripleModel; nestedRdfconfig injected fromrdf_type/__prefixes__;idusesIriIdfor explicit subject IRIssparqlmodel.rdf_bridge—model_to_graph,load_from_graph/sparql_from_graph, cycle detection, graph contract helpers (replaces interim adapter)Dual field metadata —
Field/Relationshipset bothsparql(ORM/compiler) andrdf_predicate(TripleModel) injson_schema_extraRelationship(..., cascade=False)— uses TripleModelref_fieldfor URI FK without nested embed on put
Changed
Session / hydration / serializers / FastAPI — import graph I/O from
rdf_bridgeinstead of_tripleget_prefixes()— merges built-in RDF prefixes (rdf,rdfs,xsd,schema) with model__prefixes__Removed
sparqlmodel._triple— noexec-generated shadowTripleModelclasses
Fixed
Subclasses that redeclare
id: IRIwithoutIriIdgetIriIdmetadata applied at class creation sofrom_graphpopulates subject IRIs
Migration (0.3 → 0.4)
No public API changes for SPARQLModel, Field, Relationship, or SPARQLSession.put / get / query. Internal adapter module removed; tests and apps should import sparqlmodel.rdf_bridge only if they used private _triple helpers.
[0.3.0] - 2026-05-18
Added
Session I/O via TripleModel —
put/add/get/ hydration usesparqlmodel._triple(sync_to_graph,TripleModel.from_graph) as the single mapping pathsparql_from_graph— loadSPARQLModelinstances from an rdflib graph with relationship depth (0–2)resolve_related_model— resolvestyping.ForwardRefon relationship annotations (Python 3.10–3.13)
Changed
graph.py— cascade/orphan orchestration only; interimmodel_to_triples,load_scalars, andgraph_to_modelremovedmodel_to_graph(internal) — implemented via TripleModelsync_to_graph; normalizesxsd:stringliterals for SPARQL query compatibilityHydrationError— raised only for validation / value errors duringfrom_graphhydration (ConfigurationErrorfor cycles propagates unchanged)Contract tests —
assert_put_graph_contractandtest_session_put_matches_contractassert adapter graphs only (no interim branch)
Fixed
SPARQLSession— raiseRuntimeErroron use afterclose()(CRUD,query,execute,flush,expire,rollback_pending); raise whenclose()is called with a non-empty pendingputqueueHttpStore— raiseRuntimeErroronquery/update_graphafterclose()execute()— inject sessionPREFIXdeclarations only when the query prologue has noPREFIXline (not when the word appears in a string literal)negotiated_response— respectAcceptquality values (;q=) when choosing Turtle vs JSON-LDSPARQLModel— reject duplicate RDF predicates on the same class at definition time (ConfigurationError)put/model_to_graph— detect cyclic embedded models on write (same asto_triplemodel)Orphan cleanup — do not delete embedded resources still referenced from another subject in the graph (shared composition)
put(..., flush=False)— evict identity-map entry for that subject sogetdoes not return a stale pre-pending instanceexpire()— drop matching entries from the pendingputqueueQuery compiler — reuse join variables for the same relationship path within AND / single EXISTS blocks (fixes false positives with multiple values per predicate)
JSON-LD — unwrap scalar
IRIfields from{"@id": ...}on import; omit non-cascade embedded nodes frommodel_to_jsonld
Documentation
ROADMAP / SPECS / ORM / ECOSYSTEM — mark 0.3.0 session I/O milestone shipped; multi-valued
list[...]fields remain 0.8Sessions / troubleshooting — pending
putvs identity map andclose()with pending queueQueries guide — Python
&/|precedence matches the compilerREADME / SPECS — duplicate predicates, write-path cycles, shared composition, compiler join reuse
0.2.0 - 2026-05-18
Added
HttpStore— SPARQL 1.1 over HTTP (httpx), local graph mirror, optionalsparqlmodel[http]extraSession identity map and hydration cache;
flush(),rollback_pending(),expire(model_cls, iri),put(..., flush=False),autoflushQuery compiler:
OR, ordering (<,>,<=,>=),IN(FieldRef.in_), multi-hop paths, optionalQuery.use_not_exists_for_ne()for!=sparqlmodel/_triple.py— dynamicTripleModeladapter and contract tests vs interimputgraphsRelationship(..., cascade=False)for non-owned embedsStaleTripleWarningon overlappingadd()for the same subjectsparqlmodel[fastapi]—init_app,SessionDep,http_store_lifespan,turtle_response,jsonld_response,negotiated_response
Changed
SPARQLSessionaccepts anyStoreimplementation (not onlyMemoryStore)Pluggable
Storeprotocol documented in SPECS
Fixed
AndExpr.__or__—(A & B) | Ccompiles as (A AND B) OR C, not three flat disjunctsuse_not_exists_for_ne— unique variables per!=inside AND branches of ORCompiler — URL-shaped strings on
strfields compile as literals, not IRIsSPARQLSession.get— identity map used atdepth=0when relationships are not materializedput(..., flush=False)— no longer registers identity before flush; hydration invalidated on queueflush()— re-queues pending models if a mid-flushputfails
Documentation
ROADMAP — Shipped (0.2.0); milestones 0.3–1.0 and SQLModel / SPARQLMojo parity tables
ORM, SPECS, README — HttpStore mirror, session flush/identity map, compiler ops, FastAPI extra, known limitations
PLAN, PRODUCTION — production ORM vision, checklist (P0/P1/P2), operator guide
expire(Model, iri)signature; repository URLs inpyproject.toml
0.1.4 - 2026-05-16
Added
triplemodel>=0.9.0,<2as a required dependency (mapping substrate)
Changed
Align
pydanticandrdflibpins with TripleModel (>=2.5,<3,>=7.0,<8)
Documentation
Reposition SparqlModel as a session-first SPARQL ORM (the SQLModel of SPARQL) across README and docs
Add ORM — ORM guide (lifecycle, cascade, query DSL, hydration, package choice)
Reframe PLAN, SPECS, ECOSYSTEM, and ROADMAP with ORM-first structure; TripleModel as mapping substrate
Rewrite all docs for
triplemodel>=0.9as required mapping engine; integration roadmap focuses on wiring, not adding the dependencyUpdate package metadata and module docstrings for ORM framing
Fixed
putorphan cleanup when a relationship changes from embedded model toIRIreference (old embedded triples removed)SPARQL compiler: unique join variables for parallel nested filters; reject filters on wrong model class
BNode relationship targets removed correctly on
putorphan cleanupCascade/orphan subject keys use expanded IRIs and stable
_:idBNode keys consistentlyiter_nested_modelsdedupes shared embedded resources; serialization still rejects true cyclesQuery filters: string literals no longer coerced to IRIs unless field type is
IRI; unknown compact prefixes stay literalsTyped numeric filter literals use XSD datatypes (match graph serialization)
resolve_related_modelprefersSPARQLModeloverIRIin union annotationsJSON-LD: empty
@typelist error; relationship arrays; scalarIRIfields export as@idnodesSPARQL
PREFIXdeclarations validate prefix names and namespace URIsIRI values in filters serialized via RDFLib
URIRef.n3()compile_whererejects negativelimit
0.1.3 - 2026-05-16
Fixed
putorphan cleanup now runs for embedded models (e.g. changingOrganization.located_inremoves the oldLocation)Canonical expanded IRIs used for orphan detection and cycle/visited keys (compact vs absolute mismatch)
JSON-LD:
IRIrelationship references round-trip;ensure_idon export; cycle-safe serialization via@idreferenceshydrate_from_bindingsand nestedgraph_to_modelrespectrdf:type(wrong-type bindings skipped)Query compiler: RDFLib literal escaping, IRI validation,
Nonefilter rejection, nestedAndExprflattening, invalidwhere()raisesQueryErrorQuery.limit()rejects negative valuesPer-subclass
__prefixes__copy (no shared mutable class dict)
Documented
Known limitations in README (shared embeds,
add, multi-valued predicates, JSON-LD paths, export side effects)
0.1.2 - 2026-05-16
Changed
putanddeletecascade owned-triple removal to embeddedSPARQLModelvalues and former relationship targets (orphan cleanup);IRI-only references are not cascade-deleted
0.1.1 - 2026-05-16
Fixed
Query compiler: strings with colons are only treated as IRIs when they match compact
prefix:localform;IRIvalues always serialize as IRIsCompareExpr.__and__for combining filters with&Query.first()restores_limitif execution failsHydration
depthvalidated consistently onget,query, andhydrate_from_bindings!=filters use unique SPARQL variables per expression (no collision)hydrate_oneaccepts any matchingrdf:typewhen a resource has multiple typesmodel_to_triplesdetects cyclic nested modelsJSON-LD import:
@typevalidation; nested objects no longer inherit parent keys
Documented
addinsert-only semantics and!=filter behavior
0.1.0 - 2026-05-16
Added
SPARQLModelbase class with Pydantic v2,rdf_type,Field, andRelationshipIRItype with compact/absolute expansion via namespace prefixesSPARQLSessionwithadd,put,delete,get,query, andexecuteIn-memory
MemoryStorebacked by RDFLibQuery builder with
.where(),.all(),.first(), and.limit()SPARQL compiler for scalar equality and single-hop nested filters
Graph hydration with configurable depth (0–2)
RDF serializers (Turtle, N-Triples, RDF/XML, JSON-LD) and
model_dump_jsonld()Test suite with pytest and CI (ruff, ty, coverage ≥85%)