Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 21 additions & 6 deletions rdflib/plugins/serializers/longturtle.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

from __future__ import annotations

import warnings
from typing import IO, Any, Optional

from rdflib.compare import to_canonical_graph
Expand Down Expand Up @@ -166,14 +167,14 @@ def preprocessTriple(self, triple: _TripleType) -> None:
# predicate corresponds to base namespace
continue
# Don't use generated prefixes for subjects and objects
self.getQName(node, gen_prefix=(i == VERB))
self.get_pname(node, gen_prefix=(i == VERB))
if isinstance(node, Literal) and node.datatype:
self.getQName(node.datatype, gen_prefix=_GEN_QNAME_FOR_DT)
self.get_pname(node.datatype, gen_prefix=_GEN_QNAME_FOR_DT)
p = triple[1]
if isinstance(p, BNode): # hmm - when is P ever a bnode?
self._references[p] += 1

def getQName(self, uri, gen_prefix=True):
def get_pname(self, uri, gen_prefix=True):
if not isinstance(uri, URIRef):
return None

Expand All @@ -191,16 +192,30 @@ def getQName(self, uri, gen_prefix=True):

prefix, namespace, local = parts

# To understand treatment of % character refer to Productions for terminal PLX at
# https://www.w3.org/TR/turtle/#grammar-production-PLX
# Only % NOT followed by two hex chars requires manual backslash escaping
local = local.replace(r"(", r"\(").replace(r")", r"\)")
local = self.LOCALNAME_PECRENT_CHARACTER_REQUIRING_ESCAPE_REGEX.sub(
"\\%", local
)

# QName cannot end with .
# PName cannot end with .
if local.endswith("."):
return None

prefix = self.addNamespace(prefix, namespace)

return "%s:%s" % (prefix, local)

def getQName(self, uri, gen_prefix=True):
warnings.warn(
"LongTurtleSerializer.getQName is deprecated, use LongTurtleSerializer.get_pname instead.",
DeprecationWarning,
stacklevel=2,
)
return self.get_pname(uri, gen_prefix)

def startDocument(self):
self._started = True
ns_list = sorted(self.namespaces.items())
Expand Down Expand Up @@ -256,12 +271,12 @@ def label(self, node, position):
if isinstance(node, Literal):
return node._literal_n3(
use_plain=True,
qname_callback=lambda dt: self.getQName(dt, _GEN_QNAME_FOR_DT),
qname_callback=lambda dt: self.get_pname(dt, _GEN_QNAME_FOR_DT),
)
else:
node = self.relativize(node)

return self.getQName(node, position == VERB) or node.n3()
return self.get_pname(node, position == VERB) or node.n3()

def p_squared(
self,
Expand Down
16 changes: 13 additions & 3 deletions rdflib/plugins/serializers/n3.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
Notation 3 (N3) RDF graph serializer for RDFLib.
"""

import warnings

from rdflib.graph import Graph
from rdflib.namespace import OWL, Namespace
from rdflib.plugins.serializers.turtle import OBJECT, SUBJECT, TurtleSerializer
Expand Down Expand Up @@ -47,14 +49,22 @@ def preprocessTriple(self, triple): # noqa: N802
for t in triple[2]:
self.preprocessTriple(t)

def getQName(self, uri, gen_prefix=True): # noqa: N802
def get_pname(self, uri, gen_prefix=True):
qname = None
if self.parent is not None:
qname = self.parent.getQName(uri, gen_prefix)
qname = self.parent.get_pname(uri, gen_prefix)
if qname is None:
qname = super(N3Serializer, self).getQName(uri, gen_prefix)
qname = super(N3Serializer, self).get_pname(uri, gen_prefix)
return qname

def getQName(self, uri, gen_prefix=True): # noqa: N802
warnings.warn(
"N3Serializer.getQName is deprecated, use N3Serializer.get_pname instead.",
DeprecationWarning,
stacklevel=2,
)
return self.get_pname(uri, gen_prefix)

def statement(self, subject):
self.subjectDone(subject)
properties = self.buildPredicateHash(subject)
Expand Down
4 changes: 2 additions & 2 deletions rdflib/plugins/serializers/trig.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def preprocess(self) -> None:
continue
self.store = context
# Don't generate a new prefix for a graph URI if one already exists
self.getQName(context.identifier, False)
self.get_pname(context.identifier, False)
self._subjects = {}

for triple in context:
Expand Down Expand Up @@ -103,7 +103,7 @@ def serialize(
iri = store.identifier.n3()
else:
# Show the full graph URI if a prefix for it doesn't already exist
iri = self.getQName(store.identifier, False)
iri = self.get_pname(store.identifier, False)
if iri is None:
iri = store.identifier.n3()
self.write(self.indent() + "\n%s {" % iri)
Expand Down
36 changes: 29 additions & 7 deletions rdflib/plugins/serializers/turtle.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

from __future__ import annotations

import re
import warnings
from collections import defaultdict
from typing import (
IO,
Expand Down Expand Up @@ -43,6 +45,9 @@ class RecursiveSerializer(Serializer):
maxDepth = 10
indentString = " "
roundtrip_prefixes: Tuple[Any, ...] = ()
LOCALNAME_PECRENT_CHARACTER_REQUIRING_ESCAPE_REGEX = re.compile(
r"%(?![0-9A-Fa-f]{2})"
)

def __init__(self, store: Graph):
super(RecursiveSerializer, self).__init__(store)
Expand Down Expand Up @@ -204,6 +209,9 @@ class TurtleSerializer(RecursiveSerializer):

short_name = "turtle"
indentString = " "
LOCALNAME_PECRENT_CHARACTER_REQUIRING_ESCAPE_REGEX = re.compile(
r"%(?![0-9A-Fa-f]{2})"
)

def __init__(self, store: Graph):
self._ns_rewrite: Dict[str, str] = {}
Expand Down Expand Up @@ -300,15 +308,15 @@ def preprocessTriple(self, triple: _TripleType) -> None:
# predicate corresponds to base namespace
continue
# Don't use generated prefixes for subjects and objects
self.getQName(node, gen_prefix=(i == VERB))
self.get_pname(node, gen_prefix=(i == VERB))
if isinstance(node, Literal) and node.datatype:
self.getQName(node.datatype, gen_prefix=_GEN_QNAME_FOR_DT)
self.get_pname(node.datatype, gen_prefix=_GEN_QNAME_FOR_DT)
p = triple[1]
if isinstance(p, BNode): # hmm - when is P ever a bnode?
self._references[p] += 1

# TODO: Rename to get_pname
def getQName(self, uri: Node, gen_prefix: bool = True) -> Optional[str]:
# Refer to Productions for terminals PNAME_NS and PNAME_LN https://www.w3.org/TR/turtle/#sec-grammar-grammar
def get_pname(self, uri: Node, gen_prefix: bool = True) -> Optional[str]:
if not isinstance(uri, URIRef):
return None

Expand All @@ -328,16 +336,30 @@ def getQName(self, uri: Node, gen_prefix: bool = True) -> Optional[str]:

prefix, namespace, local = parts

# To understand treatment of % character refer to Productions for terminal PLX at
# https://www.w3.org/TR/turtle/#grammar-production-PLX
# Only % NOT followed by two hex chars requires manual backslash escaping
local = local.replace(r"(", r"\(").replace(r")", r"\)")
local = self.LOCALNAME_PECRENT_CHARACTER_REQUIRING_ESCAPE_REGEX.sub(
"\\%", local
)

# QName cannot end with .
# PName cannot end with .
if local.endswith("."):
return None

prefix = self.addNamespace(prefix, namespace)

return "%s:%s" % (prefix, local)

def getQName(self, uri: Node, gen_prefix: bool = True) -> Optional[str]:
warnings.warn(
"TurtleSerializer.getQName is deprecated, use TurtleSerializer.get_pname instead.",
DeprecationWarning,
stacklevel=2,
)
return self.get_pname(uri, gen_prefix)

def startDocument(self) -> None:
self._started = True
ns_list = sorted(self.namespaces.items())
Expand Down Expand Up @@ -393,12 +415,12 @@ def label(self, node: Node, position: int) -> str:
if isinstance(node, Literal):
return node._literal_n3(
use_plain=True,
qname_callback=lambda dt: self.getQName(dt, _GEN_QNAME_FOR_DT),
qname_callback=lambda dt: self.get_pname(dt, _GEN_QNAME_FOR_DT),
)
else:
node = self.relativize(node) # type: ignore[type-var]

return self.getQName(node, position == VERB) or node.n3()
return self.get_pname(node, position == VERB) or node.n3()

def p_squared(self, node: Node, position: int, newline: bool = False) -> bool:
if (
Expand Down
4 changes: 4 additions & 0 deletions test/data/roundtrip/iri_with_escaped_percent.ttl
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
@prefix : <#> .
:zzz_\%_zzz :prop "test iri including backslash-escaped percent char \\%" .
:zzz%20zzz :prop "test iri including percent-encoded space char %20" .
:zzz\%azzz :prop "test iri including backslash-escpaed percent char followed by single hex character" .
37 changes: 0 additions & 37 deletions test/test_dataset/test_dataset_deprec_notice.py

This file was deleted.

72 changes: 72 additions & 0 deletions test/test_deprecation_notice.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import pytest

from rdflib import Dataset, Graph, Namespace, URIRef
from rdflib.plugins.serializers.longturtle import LongTurtleSerializer
from rdflib.plugins.serializers.n3 import N3Serializer
from rdflib.plugins.serializers.turtle import TurtleSerializer


def test_dataset_contexts_method():
ds = Dataset()
with pytest.warns(
DeprecationWarning,
match="Dataset.contexts is deprecated, use Dataset.graphs instead.",
):
# Call list() to consume the generator to emit the warning.
list(ds.contexts())


def test_dataset_default_context_property():
ds = Dataset()
with pytest.warns(
DeprecationWarning,
match="Dataset.default_context is deprecated, use Dataset.default_graph instead.",
):
ds.default_context

with pytest.warns(
DeprecationWarning,
match="Dataset.default_context is deprecated, use Dataset.default_graph instead.",
):
ds.default_context = ds.graph()


def test_dataset_identifier_property():
ds = Dataset()
with pytest.warns(
DeprecationWarning,
match="Dataset.identifier is deprecated and will be removed in future versions.",
):
ds.identifier


@pytest.mark.parametrize(
("serializer_cls", "warning_message"),
[
(
TurtleSerializer,
"TurtleSerializer.getQName is deprecated, use TurtleSerializer.get_pname instead.",
),
(
LongTurtleSerializer,
"LongTurtleSerializer.getQName is deprecated, use LongTurtleSerializer.get_pname instead.",
),
(
N3Serializer,
"N3Serializer.getQName is deprecated, use N3Serializer.get_pname instead.",
),
],
)
def test_serializer_getqname_method(
serializer_cls,
warning_message: str,
):
graph = Graph()
ex = Namespace("http://example.org/")
graph.bind("ex", ex)
serializer = serializer_cls(graph)

with pytest.warns(DeprecationWarning, match=warning_message):
qname = serializer.getQName(URIRef("http://example.org/value"))

assert qname == "ex:value"
1 change: 1 addition & 0 deletions test/test_roundtrip.py
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,7 @@ def test_n3_suite(
(TEST_DATA_DIR / "variants" / "diverse_quads.nq", "nquads"),
(TEST_DATA_DIR / "variants" / "diverse_quads.trig", "trig"),
(TEST_DATA_DIR / "roundtrip" / "bnode_refs.trig", "trig"),
(TEST_DATA_DIR / "roundtrip" / "iri_with_escaped_percent.ttl", "ttl"),
(TEST_DATA_DIR / "example-lots_of_graphs.n3", "n3"),
(TEST_DATA_DIR / "issue156.n3", "n3"),
]
Expand Down
2 changes: 1 addition & 1 deletion test/test_trig.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def test_graph_qname_syntax():

def test_graph_uri_syntax():
g = rdflib.Dataset()
# getQName will not abbreviate this, so it should serialize as
# get_pname will not abbreviate this, so it should serialize as
# a '<...>' term.
g.add(TRIPLE + (rdflib.URIRef("http://example.com/foo."),))
out = g.serialize(format="trig", encoding="latin-1")
Expand Down
Loading