Skip to content

Commit 0ea08f8

Browse files
committed
Expand identifierIssuer support to other API methods
1 parent c1d8094 commit 0ea08f8

7 files changed

Lines changed: 83 additions & 7 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@
66
- `pyld.FileDocumentLoader`: a document loader for local `file:` URLs with optional root confinement.
77
- `pyld.SchemeDirectedDocumentLoader`: a document loader that dispatches URL
88
strings to per-scheme loaders.
9-
- `jsonld.to_rdf()` now accepts an `identifierIssuer` option with an
10-
`IdentifierIssuer` instance for blank node identifiers. Defaults to `IdentifierIssuer('_:b')`,
11-
which preserves the previous behaviour.
9+
- `jsonld.flatten()`, `jsonld.frame()`, and `jsonld.to_rdf()` now accept an
10+
`identifierIssuer` option with an `IdentifierIssuer` instance for blank node
11+
identifiers. Defaults to `IdentifierIssuer('_:b')`, which preserves the
12+
previous behaviour.
1213

1314
## 3.2.0 - 2026-08-17
1415

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ issuer = IdentifierIssuer('_:doc')
133133
rdf = jsonld.to_rdf(
134134
doc,
135135
{'identifierIssuer': issuer, 'format': 'application/n-quads'})
136+
# flatten() and frame() accept the same identifierIssuer option
136137
```
137138

138139
## Features & conformance

docs/reference/flatten.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
show_bases: false
1313
heading_level: 3
1414

15+
Pass an `IdentifierIssuer` instance as the `identifierIssuer` option to control
16+
blank node identifiers generated during flattening.
17+
1518
## Example
1619

1720
{{ example('flatten.py', 'json') }}

docs/reference/frame.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
show_bases: false
1313
heading_level: 3
1414

15+
Pass an `IdentifierIssuer` instance as the `identifierIssuer` option to control
16+
blank node identifiers generated during framing.
17+
1518
## Example
1619

1720
{{ example('frame.py', 'json') }}

lib/pyld/jsonld.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,8 @@ def link(input_, ctx, options=None):
250250
defaults to 'json-ld-1.1'.
251251
[documentLoader(url, options)] the document loader
252252
(default: _default_document_loader).
253+
[identifierIssuer] an IdentifierIssuer instance to use for blank node
254+
identifiers (default: IdentifierIssuer('_:b')).
253255
254256
:return: the linked JSON-LD output.
255257
"""
@@ -685,6 +687,8 @@ def flatten(self, input_, ctx, options):
685687
defaults to 'json-ld-1.1'.
686688
[documentLoader(url, options)] the document loader
687689
(default: _default_document_loader).
690+
[identifierIssuer] an IdentifierIssuer instance to use for blank node
691+
identifiers (default: IdentifierIssuer('_:b')).
688692
689693
:return: the flattened JSON-LD output.
690694
"""
@@ -697,6 +701,7 @@ def flatten(self, input_, ctx, options):
697701
)
698702
options.setdefault('extractAllScripts', True)
699703
options.setdefault('processingMode', 'json-ld-1.1')
704+
options.setdefault('identifierIssuer', IdentifierIssuer('_:b'))
700705

701706
try:
702707
# expand input
@@ -707,7 +712,7 @@ def flatten(self, input_, ctx, options):
707712
) from cause
708713

709714
# do flattening
710-
flattened = self._flatten(expanded)
715+
flattened = self._flatten(expanded, options)
711716

712717
if ctx is None:
713718
return flattened
@@ -750,6 +755,8 @@ def frame(self, input_, frame, options):
750755
[requireAll] default @requireAll flag (default: False).
751756
[documentLoader(url, options)] the document loader
752757
(default: _default_document_loader).
758+
[identifierIssuer] an IdentifierIssuer instance to use for blank node
759+
identifiers (default: IdentifierIssuer('_:b')).
753760
754761
:return: the framed JSON-LD output.
755762
"""
@@ -769,6 +776,7 @@ def frame(self, input_, frame, options):
769776
)
770777
options.setdefault('extractAllScripts', False)
771778
options.setdefault('processingMode', 'json-ld-1.1')
779+
options.setdefault('identifierIssuer', IdentifierIssuer('_:b'))
772780

773781
# if frame is a string, attempt to dereference remote document
774782
if _is_string(frame):
@@ -901,6 +909,8 @@ def normalize(self, input_, options):
901909
)
902910
options.setdefault('extractAllScripts', True)
903911
options.setdefault('processingMode', 'json-ld-1.1')
912+
# Prevent a custom identifier issuer accidentally messing up normalization.
913+
options.pop('identifierIssuer', None)
904914

905915
if options['algorithm'] not in ['URDNA2015', 'URGNA2012']:
906916
raise JsonLdError(
@@ -2845,7 +2855,7 @@ def _prepare_nested_context(self, active_ctx, element, options):
28452855

28462856
return active_ctx, type_key, type_scoped_ctx
28472857

2848-
def _flatten(self, input):
2858+
def _flatten(self, input, options):
28492859
"""
28502860
Performs JSON-LD flattening.
28512861
@@ -2854,7 +2864,7 @@ def _flatten(self, input):
28542864
:return: the flattened JSON-LD output.
28552865
"""
28562866
# produce a map of all subjects and label each bnode
2857-
issuer = IdentifierIssuer('_:b')
2867+
issuer = options['identifierIssuer'] or IdentifierIssuer('_:b')
28582868
graphs = {'@default': {}}
28592869
self._create_node_map(input, graphs, '@default', issuer)
28602870

@@ -2900,7 +2910,7 @@ def _frame(self, input_, frame, options):
29002910
}
29012911

29022912
# produce a map of all graphs and name each bnode
2903-
issuer = IdentifierIssuer('_:b')
2913+
issuer = options['identifierIssuer'] or IdentifierIssuer('_:b')
29042914
self._create_node_map(input_, state['graphMap'], '@default', issuer)
29052915
if options['merged']:
29062916
state['graphMap']['@merged'] = self._merge_node_map_graphs(

lib/pyld/options.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,9 @@ class FlattenOptions(ExpandContextOptions, total=False):
101101
expandContext: Context
102102
"""A context to expand with."""
103103

104+
identifierIssuer: IdentifierIssuer
105+
"""An identifier issuer to use for blank node identifiers (default: `IdentifierIssuer('_:b')`)."""
106+
104107

105108
class FrameOptions(ExpandContextOptions, total=False):
106109
documentLoader: DocumentLoader | DocumentLoaderCallable
@@ -133,6 +136,9 @@ class FrameOptions(ExpandContextOptions, total=False):
133136
requireAll: bool
134137
"""Default `@requireAll` flag (default: `False`)."""
135138

139+
identifierIssuer: IdentifierIssuer
140+
"""An identifier issuer to use for blank node identifiers (default: `IdentifierIssuer('_:b')`)."""
141+
136142

137143
class NormalizeOptions(ProcessingOptions, total=False):
138144
documentLoader: DocumentLoader | DocumentLoaderCallable

tests/test_jsonld.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -630,6 +630,22 @@ def test_processing_id_in_inner_context(self):
630630
assert "namespace" in output_json["system"][0]
631631
assert "uri" in output_json["system"][0]["contents"][0]
632632

633+
def test_frame_uses_identifier_issuer_option(self):
634+
input = {'http://example.org/p': {'@id': '_:old'}}
635+
636+
result = jsonld.frame(
637+
input,
638+
{},
639+
options={'identifierIssuer': IdentifierIssuer('_:custom')},
640+
)
641+
642+
assert result == {
643+
'@graph': [
644+
{'http://example.org/p': {'@id': '_:custom1'}},
645+
{'@id': '_:custom1'},
646+
]
647+
}
648+
633649
# PR: https://github.com/digitalbazaar/pyld/pull/31
634650

635651
FRAME_0001_IN = {
@@ -933,6 +949,42 @@ def test_circular_references_link_and_embed(self):
933949
jsonld.frame(input, frame)
934950

935951

952+
class TestFlatten:
953+
def test_flatten_uses_identifier_issuer_option(self):
954+
input = {'http://example.org/p': {'@id': '_:old'}}
955+
956+
result = jsonld.flatten(
957+
input,
958+
options={'identifierIssuer': IdentifierIssuer('_:custom')},
959+
)
960+
961+
assert result == [
962+
{
963+
'@id': '_:custom0',
964+
'http://example.org/p': [{'@id': '_:custom1'}],
965+
}
966+
]
967+
968+
969+
class TestNormalize:
970+
def test_normalize_does_not_pass_identifier_issuer_to_to_rdf(self):
971+
class RaisingIdentifierIssuer(IdentifierIssuer):
972+
def get_id(self, old=None):
973+
raise AssertionError('identifierIssuer leaked into normalize')
974+
975+
input = {'http://example.org/p': {'@id': '_:old'}}
976+
977+
result = jsonld.normalize(
978+
input,
979+
options={
980+
'format': 'application/n-quads',
981+
'identifierIssuer': RaisingIdentifierIssuer('_:custom'),
982+
},
983+
)
984+
985+
assert result == '_:c14n1 <http://example.org/p> _:c14n0 .\n'
986+
987+
936988
class TestToRdf:
937989
# PR: https://github.com/digitalbazaar/pyld/pull/202
938990

0 commit comments

Comments
 (0)