From 0c026025702650c6d324700e614d11ebde9a68f3 Mon Sep 17 00:00:00 2001 From: lisat-dstg <204978408+lisat-dstg@users.noreply.github.com> Date: Fri, 9 Jan 2026 05:39:01 +0000 Subject: [PATCH 01/10] #1395 add breaking test artefact for makes invalid turtle cURIs --- test/data/roundtrip/iri_with_escaped_char.ttl | 4 ++++ test/test_roundtrip.py | 1 + 2 files changed, 5 insertions(+) create mode 100644 test/data/roundtrip/iri_with_escaped_char.ttl diff --git a/test/data/roundtrip/iri_with_escaped_char.ttl b/test/data/roundtrip/iri_with_escaped_char.ttl new file mode 100644 index 0000000000..d26e5660e1 --- /dev/null +++ b/test/data/roundtrip/iri_with_escaped_char.ttl @@ -0,0 +1,4 @@ +@prefix : <#> . +:foo_\%_bar :prop "test iri including escaped char %" . +:foo_\&_bar :prop "test iri including escaped char &" . +:foo_\/_bar :prop "test iri including escaped char /" . diff --git a/test/test_roundtrip.py b/test/test_roundtrip.py index bff01b2850..9b6aaddc9f 100644 --- a/test/test_roundtrip.py +++ b/test/test_roundtrip.py @@ -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_char.ttl", "ttl"), (TEST_DATA_DIR / "example-lots_of_graphs.n3", "n3"), (TEST_DATA_DIR / "issue156.n3", "n3"), ] From c1783c40ffb5e79088d7a7c87c6df0e84b5f5d20 Mon Sep 17 00:00:00 2001 From: Lisa <204978408+lisat-dstg@users.noreply.github.com> Date: Mon, 12 Jan 2026 14:48:21 +1030 Subject: [PATCH 02/10] #1395 fix turtle serialisation to backslash-escape required special characters in local names of IRIs --- rdflib/plugins/serializers/turtle.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rdflib/plugins/serializers/turtle.py b/rdflib/plugins/serializers/turtle.py index 9a77debb9b..05c2a8041e 100644 --- a/rdflib/plugins/serializers/turtle.py +++ b/rdflib/plugins/serializers/turtle.py @@ -26,6 +26,7 @@ from rdflib.namespace import RDF, RDFS from rdflib.serializer import Serializer from rdflib.term import BNode, Literal, Node, URIRef +import re _StrT = TypeVar("_StrT", bound=str) @@ -328,7 +329,7 @@ def getQName(self, uri: Node, gen_prefix: bool = True) -> Optional[str]: prefix, namespace, local = parts - local = local.replace(r"(", r"\(").replace(r")", r"\)") + local = re.sub(r'[\'_~.\-!$&"\(\)*+,;=/\?#@%]', r'\\\0', local) # QName cannot end with . if local.endswith("."): From bf362e4349f89477ed2387120ff8e3f5b6dacb1d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 12 Jan 2026 04:32:54 +0000 Subject: [PATCH 03/10] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- rdflib/plugins/serializers/turtle.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rdflib/plugins/serializers/turtle.py b/rdflib/plugins/serializers/turtle.py index 05c2a8041e..e2865adfd7 100644 --- a/rdflib/plugins/serializers/turtle.py +++ b/rdflib/plugins/serializers/turtle.py @@ -5,6 +5,7 @@ from __future__ import annotations +import re from collections import defaultdict from typing import ( IO, @@ -26,7 +27,6 @@ from rdflib.namespace import RDF, RDFS from rdflib.serializer import Serializer from rdflib.term import BNode, Literal, Node, URIRef -import re _StrT = TypeVar("_StrT", bound=str) @@ -329,7 +329,7 @@ def getQName(self, uri: Node, gen_prefix: bool = True) -> Optional[str]: prefix, namespace, local = parts - local = re.sub(r'[\'_~.\-!$&"\(\)*+,;=/\?#@%]', r'\\\0', local) + local = re.sub(r'[\'_~.\-!$&"\(\)*+,;=/\?#@%]', r"\\\0", local) # QName cannot end with . if local.endswith("."): From decadf56f1bbe9a091b2af01343ddfc61974f208 Mon Sep 17 00:00:00 2001 From: Lisa <204978408+lisat-dstg@users.noreply.github.com> Date: Tue, 13 Jan 2026 08:51:30 +1030 Subject: [PATCH 04/10] #1395 update test cases --- test/data/roundtrip/iri_with_escaped_char.ttl | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/test/data/roundtrip/iri_with_escaped_char.ttl b/test/data/roundtrip/iri_with_escaped_char.ttl index d26e5660e1..58140f6329 100644 --- a/test/data/roundtrip/iri_with_escaped_char.ttl +++ b/test/data/roundtrip/iri_with_escaped_char.ttl @@ -1,4 +1,18 @@ @prefix : <#> . -:foo_\%_bar :prop "test iri including escaped char %" . +:foo_\'_bar :prop "test iri including escaped char '" . +:foo_\~_bar :prop "test iri including escaped char ~" . +:foo_\!_bar :prop "test iri including escaped char !" . +:foo_\$_bar :prop "test iri including escaped char $" . :foo_\&_bar :prop "test iri including escaped char &" . +:foo_\(_bar :prop "test iri including escaped char (" . +:foo_\)_bar :prop "test iri including escaped char )" . +:foo_\*_bar :prop "test iri including escaped char *" . +:foo_\+_bar :prop "test iri including escaped char +" . +:foo_\,_bar :prop "test iri including escaped char ," . +:foo_\;_bar :prop "test iri including escaped char ;" . +:foo_\=_bar :prop "test iri including escaped char =" . :foo_\/_bar :prop "test iri including escaped char /" . +:foo_\?_bar :prop "test iri including escaped char ?" . +:foo_\#_bar :prop "test iri including escaped char #" . +:foo_\@_bar :prop "test iri including escaped char @" . +:foo_\%_bar :prop "test iri including escaped char %" . From 82e6ad9af32f9fbd9b03ef21199002033c518498 Mon Sep 17 00:00:00 2001 From: Lisa <204978408+lisat-dstg@users.noreply.github.com> Date: Tue, 13 Jan 2026 08:53:31 +1030 Subject: [PATCH 05/10] fix: Update regex for special characters in IRI localname that need backslash escaping --- rdflib/plugins/serializers/turtle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rdflib/plugins/serializers/turtle.py b/rdflib/plugins/serializers/turtle.py index e2865adfd7..7509315b7b 100644 --- a/rdflib/plugins/serializers/turtle.py +++ b/rdflib/plugins/serializers/turtle.py @@ -329,7 +329,7 @@ def getQName(self, uri: Node, gen_prefix: bool = True) -> Optional[str]: prefix, namespace, local = parts - local = re.sub(r'[\'_~.\-!$&"\(\)*+,;=/\?#@%]', r"\\\0", local) + local = re.sub(r"[\"'~!$&\(\)*+,;=/\?#@%]", r"\\\0", local) # QName cannot end with . if local.endswith("."): From 8bd74e5dd2f6457435a34205212c4f356147c9f5 Mon Sep 17 00:00:00 2001 From: lisat <204978408+lisat-dstg@users.noreply.github.com> Date: Fri, 23 Jan 2026 09:13:32 +0000 Subject: [PATCH 06/10] fix: More focused test; more focused fix; Also renamed getQName fn to get_pname incl docs #1395 --- rdflib/plugins/serializers/longturtle.py | 16 +++++++++------ rdflib/plugins/serializers/n3.py | 6 +++--- rdflib/plugins/serializers/trig.py | 4 ++-- rdflib/plugins/serializers/turtle.py | 20 +++++++++++-------- test/data/roundtrip/iri_with_escaped_char.ttl | 18 ----------------- .../roundtrip/iri_with_escaped_percent.ttl | 4 ++++ test/test_roundtrip.py | 2 +- test/test_trig.py | 2 +- 8 files changed, 33 insertions(+), 39 deletions(-) delete mode 100644 test/data/roundtrip/iri_with_escaped_char.ttl create mode 100644 test/data/roundtrip/iri_with_escaped_percent.ttl diff --git a/rdflib/plugins/serializers/longturtle.py b/rdflib/plugins/serializers/longturtle.py index 3800c40dd7..af99ebd290 100644 --- a/rdflib/plugins/serializers/longturtle.py +++ b/rdflib/plugins/serializers/longturtle.py @@ -166,14 +166,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 @@ -191,9 +191,13 @@ 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 @@ -256,12 +260,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, diff --git a/rdflib/plugins/serializers/n3.py b/rdflib/plugins/serializers/n3.py index 627bbe19ca..de697d68e8 100644 --- a/rdflib/plugins/serializers/n3.py +++ b/rdflib/plugins/serializers/n3.py @@ -47,12 +47,12 @@ 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): # noqa: N802 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 statement(self, subject): diff --git a/rdflib/plugins/serializers/trig.py b/rdflib/plugins/serializers/trig.py index 25f8d2a128..2c3fe2be16 100644 --- a/rdflib/plugins/serializers/trig.py +++ b/rdflib/plugins/serializers/trig.py @@ -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: @@ -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) diff --git a/rdflib/plugins/serializers/turtle.py b/rdflib/plugins/serializers/turtle.py index 7509315b7b..649a04c0ff 100644 --- a/rdflib/plugins/serializers/turtle.py +++ b/rdflib/plugins/serializers/turtle.py @@ -199,12 +199,12 @@ def relativize(self, uri: _StrT) -> Union[_StrT, URIRef]: _GEN_QNAME_FOR_DT = False _SPACIOUS_OUTPUT = False - class TurtleSerializer(RecursiveSerializer): """Turtle RDF graph serializer.""" 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] = {} @@ -301,15 +301,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 @@ -329,7 +329,11 @@ def getQName(self, uri: Node, gen_prefix: bool = True) -> Optional[str]: prefix, namespace, local = parts - local = re.sub(r"[\"'~!$&\(\)*+,;=/\?#@%]", r"\\\0", local) + # 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 . if local.endswith("."): @@ -394,12 +398,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 ( diff --git a/test/data/roundtrip/iri_with_escaped_char.ttl b/test/data/roundtrip/iri_with_escaped_char.ttl deleted file mode 100644 index 58140f6329..0000000000 --- a/test/data/roundtrip/iri_with_escaped_char.ttl +++ /dev/null @@ -1,18 +0,0 @@ -@prefix : <#> . -:foo_\'_bar :prop "test iri including escaped char '" . -:foo_\~_bar :prop "test iri including escaped char ~" . -:foo_\!_bar :prop "test iri including escaped char !" . -:foo_\$_bar :prop "test iri including escaped char $" . -:foo_\&_bar :prop "test iri including escaped char &" . -:foo_\(_bar :prop "test iri including escaped char (" . -:foo_\)_bar :prop "test iri including escaped char )" . -:foo_\*_bar :prop "test iri including escaped char *" . -:foo_\+_bar :prop "test iri including escaped char +" . -:foo_\,_bar :prop "test iri including escaped char ," . -:foo_\;_bar :prop "test iri including escaped char ;" . -:foo_\=_bar :prop "test iri including escaped char =" . -:foo_\/_bar :prop "test iri including escaped char /" . -:foo_\?_bar :prop "test iri including escaped char ?" . -:foo_\#_bar :prop "test iri including escaped char #" . -:foo_\@_bar :prop "test iri including escaped char @" . -:foo_\%_bar :prop "test iri including escaped char %" . diff --git a/test/data/roundtrip/iri_with_escaped_percent.ttl b/test/data/roundtrip/iri_with_escaped_percent.ttl new file mode 100644 index 0000000000..7fb84ecec3 --- /dev/null +++ b/test/data/roundtrip/iri_with_escaped_percent.ttl @@ -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" . diff --git a/test/test_roundtrip.py b/test/test_roundtrip.py index 9b6aaddc9f..8dc951b6cd 100644 --- a/test/test_roundtrip.py +++ b/test/test_roundtrip.py @@ -526,7 +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_char.ttl", "ttl"), + (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"), ] diff --git a/test/test_trig.py b/test/test_trig.py index afcf2c4cff..1c24a18d86 100644 --- a/test/test_trig.py +++ b/test/test_trig.py @@ -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") From 7272656aeeb8849bb4b799c0e4255aa9cda40d27 Mon Sep 17 00:00:00 2001 From: lisat <204978408+lisat-dstg@users.noreply.github.com> Date: Fri, 23 Jan 2026 09:19:37 +0000 Subject: [PATCH 07/10] style: format with ruff #1395 --- rdflib/plugins/serializers/longturtle.py | 4 +++- rdflib/plugins/serializers/turtle.py | 9 +++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/rdflib/plugins/serializers/longturtle.py b/rdflib/plugins/serializers/longturtle.py index af99ebd290..58e4b9d2f0 100644 --- a/rdflib/plugins/serializers/longturtle.py +++ b/rdflib/plugins/serializers/longturtle.py @@ -195,7 +195,9 @@ def get_pname(self, uri, gen_prefix=True): # 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) + local = self.LOCALNAME_PECRENT_CHARACTER_REQUIRING_ESCAPE_REGEX.sub( + "\\%", local + ) # PName cannot end with . if local.endswith("."): diff --git a/rdflib/plugins/serializers/turtle.py b/rdflib/plugins/serializers/turtle.py index 649a04c0ff..c4613c3ad8 100644 --- a/rdflib/plugins/serializers/turtle.py +++ b/rdflib/plugins/serializers/turtle.py @@ -199,12 +199,15 @@ def relativize(self, uri: _StrT) -> Union[_StrT, URIRef]: _GEN_QNAME_FOR_DT = False _SPACIOUS_OUTPUT = False + class TurtleSerializer(RecursiveSerializer): """Turtle RDF graph serializer.""" short_name = "turtle" indentString = " " - LOCALNAME_PECRENT_CHARACTER_REQUIRING_ESCAPE_REGEX = re.compile(r"%(?![0-9A-Fa-f]{2})") + 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] = {} @@ -333,7 +336,9 @@ def get_pname(self, uri: Node, gen_prefix: bool = True) -> Optional[str]: # 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) + local = self.LOCALNAME_PECRENT_CHARACTER_REQUIRING_ESCAPE_REGEX.sub( + "\\%", local + ) # QName cannot end with . if local.endswith("."): From 6cb2425edc6f5e204fdc8624508acbf938e527c4 Mon Sep 17 00:00:00 2001 From: lisat <204978408+lisat-dstg@users.noreply.github.com> Date: Fri, 23 Jan 2026 09:26:01 +0000 Subject: [PATCH 08/10] style: another ruff fix #1395 --- rdflib/plugins/serializers/n3.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rdflib/plugins/serializers/n3.py b/rdflib/plugins/serializers/n3.py index de697d68e8..809c025d67 100644 --- a/rdflib/plugins/serializers/n3.py +++ b/rdflib/plugins/serializers/n3.py @@ -47,7 +47,7 @@ def preprocessTriple(self, triple): # noqa: N802 for t in triple[2]: self.preprocessTriple(t) - def get_pname(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.get_pname(uri, gen_prefix) From aad5a5361f7b022ba42bb26a7a815700a59fd335 Mon Sep 17 00:00:00 2001 From: Lisa <204978408+lisat-dstg@users.noreply.github.com> Date: Mon, 2 Feb 2026 16:47:20 +1030 Subject: [PATCH 09/10] fix: Move 'percent' regex from turtle serializer to parent class #1395 --- rdflib/plugins/serializers/turtle.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rdflib/plugins/serializers/turtle.py b/rdflib/plugins/serializers/turtle.py index c4613c3ad8..9fbbf23662 100644 --- a/rdflib/plugins/serializers/turtle.py +++ b/rdflib/plugins/serializers/turtle.py @@ -44,6 +44,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) From 547ce3c36a51de956031f1c2aa82f0e4de6acb4b Mon Sep 17 00:00:00 2001 From: Edmond Chuc Date: Fri, 13 Feb 2026 12:39:58 +1000 Subject: [PATCH 10/10] chore: add getQName method back and add deprec notice --- rdflib/plugins/serializers/longturtle.py | 9 +++ rdflib/plugins/serializers/n3.py | 10 +++ rdflib/plugins/serializers/turtle.py | 11 ++- .../test_dataset_deprec_notice.py | 37 ---------- test/test_deprecation_notice.py | 72 +++++++++++++++++++ 5 files changed, 101 insertions(+), 38 deletions(-) delete mode 100644 test/test_dataset/test_dataset_deprec_notice.py create mode 100644 test/test_deprecation_notice.py diff --git a/rdflib/plugins/serializers/longturtle.py b/rdflib/plugins/serializers/longturtle.py index 58e4b9d2f0..d35485c911 100644 --- a/rdflib/plugins/serializers/longturtle.py +++ b/rdflib/plugins/serializers/longturtle.py @@ -18,6 +18,7 @@ from __future__ import annotations +import warnings from typing import IO, Any, Optional from rdflib.compare import to_canonical_graph @@ -207,6 +208,14 @@ def get_pname(self, uri, gen_prefix=True): 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()) diff --git a/rdflib/plugins/serializers/n3.py b/rdflib/plugins/serializers/n3.py index 809c025d67..dc45869cc8 100644 --- a/rdflib/plugins/serializers/n3.py +++ b/rdflib/plugins/serializers/n3.py @@ -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 @@ -55,6 +57,14 @@ def get_pname(self, uri, gen_prefix=True): 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) diff --git a/rdflib/plugins/serializers/turtle.py b/rdflib/plugins/serializers/turtle.py index 9fbbf23662..5a76992fb9 100644 --- a/rdflib/plugins/serializers/turtle.py +++ b/rdflib/plugins/serializers/turtle.py @@ -6,6 +6,7 @@ from __future__ import annotations import re +import warnings from collections import defaultdict from typing import ( IO, @@ -343,7 +344,7 @@ def get_pname(self, uri: Node, gen_prefix: bool = True) -> Optional[str]: "\\%", local ) - # QName cannot end with . + # PName cannot end with . if local.endswith("."): return None @@ -351,6 +352,14 @@ def get_pname(self, uri: Node, gen_prefix: bool = True) -> Optional[str]: 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()) diff --git a/test/test_dataset/test_dataset_deprec_notice.py b/test/test_dataset/test_dataset_deprec_notice.py deleted file mode 100644 index 068ddd3c25..0000000000 --- a/test/test_dataset/test_dataset_deprec_notice.py +++ /dev/null @@ -1,37 +0,0 @@ -import pytest - -from rdflib import Dataset - - -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 diff --git a/test/test_deprecation_notice.py b/test/test_deprecation_notice.py new file mode 100644 index 0000000000..19190635ef --- /dev/null +++ b/test/test_deprecation_notice.py @@ -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"