"""GraphPattern — composable SPARQL WHERE block."""from__future__importannotationsimportcopyfromtypingimportTYPE_CHECKING,Anyfrom._termimportTerm,serialize_termifTYPE_CHECKING:from._builderimportSelectQuerydef_ensure_var(var:str)->str:"""Prefix *var* with ``?`` if missing."""returnvarifvar.startswith("?")elsef"?{var}"
[docs]classGraphPattern:"""A composable block of SPARQL graph patterns. Every mutating method returns ``self`` for fluent chaining. """def__init__(self)->None:self._elements:list[str]=[]
[docs]defwhere(self,s:Term,p:Term,o:Term)->GraphPattern:"""Add a triple pattern."""self._elements.append(f"{serialize_term(s)}{serialize_term(p)}{serialize_term(o)} .")returnself
[docs]deffilter(self,expr:str)->GraphPattern:"""Add a ``FILTER(expr)`` clause."""self._elements.append(f"FILTER({expr})")returnself
[docs]defoptional(self,s_or_pattern:Term|GraphPattern,p:Term|None=None,o:Term|None=None,)->GraphPattern:"""Add an ``OPTIONAL { … }`` block. - ``optional(s, p, o)`` — single triple shorthand - ``optional(GraphPattern())`` — complex pattern block """ifisinstance(s_or_pattern,GraphPattern):body=s_or_pattern.to_sparql(indent=4)self._elements.append(f"OPTIONAL {{\n{body}\n}}")else:ifpisNoneoroisNone:raiseValueError("optional() requires either a GraphPattern or three term arguments (s, p, o)")triple=f"{serialize_term(s_or_pattern)}{serialize_term(p)}{serialize_term(o)} ."self._elements.append(f"OPTIONAL {{{triple}}}")returnself
[docs]defunion(self,*patterns:GraphPattern)->GraphPattern:"""Add ``{ … } UNION { … }`` blocks."""iflen(patterns)<2:raiseValueError("union() requires at least two GraphPattern arguments")parts=[]forpatinpatterns:body=pat.to_sparql(indent=4)parts.append(f"{{\n{body}\n}}")self._elements.append(" UNION ".join(parts))returnself
[docs]defbind(self,expr:str,var:str)->GraphPattern:"""Add a ``BIND(expr AS ?var)`` clause."""self._elements.append(f"BIND({expr} AS {_ensure_var(var)})")returnself
[docs]defvalues(self,var:str,vals:list[Any])->GraphPattern:"""Add a ``VALUES ?var { … }`` clause."""serialized=" ".join(serialize_term(val)forvalinvals)self._elements.append(f"VALUES {_ensure_var(var)}{{{serialized}}}")returnself
[docs]defsub_query(self,builder:SelectQuery)->GraphPattern:"""Embed a sub-SELECT inside this pattern."""inner=builder.build()indented="\n".join(f" {line}"forlineininner.splitlines())self._elements.append(f"{{\n{indented}\n}}")returnself
[docs]defto_sparql(self,indent:int=2)->str:"""Render the pattern body (without the outer braces)."""prefix=" "*indentreturn"\n".join(f"{prefix}{el}"forelinself._elements)
[docs]defcopy(self)->GraphPattern:"""Return a deep copy of this pattern."""returncopy.deepcopy(self)