From a68d6ba21b0fec230c29fbe7a64f73a7cc6e0804 Mon Sep 17 00:00:00 2001 From: Anatoly Scherbakov Date: Fri, 10 Apr 2026 23:41:57 +0400 Subject: [PATCH 1/8] Guard active `@base` use in `_expand_iri` when `base` is not None --- lib/pyld/jsonld.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/pyld/jsonld.py b/lib/pyld/jsonld.py index ce18f0d3..30cc6c89 100644 --- a/lib/pyld/jsonld.py +++ b/lib/pyld/jsonld.py @@ -5842,8 +5842,7 @@ def _expand_iri( # resolve against base rval = value - # if there is a base in the active context, use that - if '@base' in active_ctx: + if base is not None and '@base' in active_ctx: # the None case preserves rval as potentially relative if active_ctx['@base'] is not None: resolved_base = ( @@ -5856,7 +5855,6 @@ def _expand_iri( # we should fallback to document base elif base == '': rval = resolve(rval, DEFAULT_BASE_IRI) - # in other cases, use the base that was passed to this function elif base: rval = resolve(rval, base) From 8c5df768c4b669a46d396ded6f68523fe4f9e07b Mon Sep 17 00:00:00 2001 From: Anatoly Scherbakov Date: Fri, 10 Apr 2026 23:42:00 +0400 Subject: [PATCH 2/8] Add regression test for `@base` vs property term expansion --- tests/test_base_vs_vocab.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 tests/test_base_vs_vocab.py diff --git a/tests/test_base_vs_vocab.py b/tests/test_base_vs_vocab.py new file mode 100644 index 00000000..cffd08d0 --- /dev/null +++ b/tests/test_base_vs_vocab.py @@ -0,0 +1,33 @@ +""" +Regression test: @base must not be used to expand property keys. + +Property names are expanded vocabulary-relative: @vocab, term definitions, +compact IRIs with a defined prefix, etc. The active context's @base is for +document-relative IRI resolution where the algorithms pass that flag (e.g. +certain @id and @type values), not for turning arbitrary keys into absolute +IRIs. Here the context sets only @base; `name` has no term definition and no +@vocab, so it cannot become an absolute property IRI and must be dropped. + +See: https://www.w3.org/TR/json-ld11-api/#iri-expansion +""" + +import pyld.jsonld as jsonld + + +def test_base_does_not_expand_property_terms(): + """Property keys must not be resolved against @base without vocabulary-relative mapping.""" + doc = { + '@context': {'@base': 'https://schema.org/'}, + '@id': 'https://w3.org/yaml-ld/', + '@type': 'WebContent', + 'name': 'YAML-LD', + } + result = jsonld.expand(doc) + # `name` has no vocabulary-relative mapping (@vocab or term definition); + # @base must not supply one. The key is dropped. + assert result == [ + { + '@id': 'https://w3.org/yaml-ld/', + '@type': ['https://schema.org/WebContent'], + } + ] From e844a7973b9698ebf70436441926ba3100de212e Mon Sep 17 00:00:00 2001 From: Anatoly Scherbakov Date: Fri, 10 Apr 2026 23:50:03 +0400 Subject: [PATCH 3/8] Install `libxml2-dev` and `libxslt1-dev` in CI for `lxml` builds --- .github/workflows/main.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 0f7438b2..43ad16fa 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -48,6 +48,10 @@ jobs: with: submodules: recursive persist-credentials: false + - name: Install system libraries for lxml + run: | + sudo apt-get update + sudo apt-get install -y libxml2-dev libxslt1-dev - name: Use Python ${{ matrix.python-version }} uses: actions/setup-python@v6 with: @@ -78,6 +82,10 @@ jobs: with: submodules: recursive persist-credentials: false + - name: Install system libraries for lxml + run: | + sudo apt-get update + sudo apt-get install -y libxml2-dev libxslt1-dev - name: Set up Python uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 with: From 618b4a29a070fe38c844b366cf1305f6c92a8c9d Mon Sep 17 00:00:00 2001 From: Anatoly Scherbakov Date: Sun, 26 Apr 2026 19:24:10 +0400 Subject: [PATCH 4/8] Move `@base` vs property term regression test into `TestExpand` --- tests/test_jsonld.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/test_jsonld.py b/tests/test_jsonld.py index a12016fe..578e3a79 100644 --- a/tests/test_jsonld.py +++ b/tests/test_jsonld.py @@ -180,6 +180,36 @@ def test_missing_base(self): got = jsonld.expand(input) assert got == expected + def test_base_does_not_expand_property_terms(self): + """ + Regression test: @base must not be used to expand property keys. + + Property names are expanded vocabulary-relative: @vocab, term + definitions, compact IRIs with a defined prefix, etc. The active + context's @base is for document-relative IRI resolution where the + algorithms pass that flag (e.g. certain @id and @type values), not + for turning arbitrary keys into absolute IRIs. Here the context sets + only @base; `name` has no term definition and no @vocab, so it + cannot become an absolute property IRI and must be dropped. + + See: https://www.w3.org/TR/json-ld11-api/#iri-expansion + """ + doc = { + '@context': {'@base': 'https://schema.org/'}, + '@id': 'https://w3.org/yaml-ld/', + '@type': 'WebContent', + 'name': 'YAML-LD', + } + result = jsonld.expand(doc) + # `name` has no vocabulary-relative mapping (@vocab or term + # definition); @base must not supply one. The key is dropped. + assert result == [ + { + '@id': 'https://w3.org/yaml-ld/', + '@type': ['https://schema.org/WebContent'], + } + ] + class TestFrame: # Issue 11 - PR: https://github.com/digitalbazaar/pyld/issues/149 From 6055983d7d735fc205ea2f81d9d26554c3659e02 Mon Sep 17 00:00:00 2001 From: Anatoly Scherbakov Date: Sun, 26 Apr 2026 19:24:27 +0400 Subject: [PATCH 5/8] Remove standalone `tests/test_base_vs_vocab.py` --- tests/test_base_vs_vocab.py | 33 --------------------------------- 1 file changed, 33 deletions(-) delete mode 100644 tests/test_base_vs_vocab.py diff --git a/tests/test_base_vs_vocab.py b/tests/test_base_vs_vocab.py deleted file mode 100644 index cffd08d0..00000000 --- a/tests/test_base_vs_vocab.py +++ /dev/null @@ -1,33 +0,0 @@ -""" -Regression test: @base must not be used to expand property keys. - -Property names are expanded vocabulary-relative: @vocab, term definitions, -compact IRIs with a defined prefix, etc. The active context's @base is for -document-relative IRI resolution where the algorithms pass that flag (e.g. -certain @id and @type values), not for turning arbitrary keys into absolute -IRIs. Here the context sets only @base; `name` has no term definition and no -@vocab, so it cannot become an absolute property IRI and must be dropped. - -See: https://www.w3.org/TR/json-ld11-api/#iri-expansion -""" - -import pyld.jsonld as jsonld - - -def test_base_does_not_expand_property_terms(): - """Property keys must not be resolved against @base without vocabulary-relative mapping.""" - doc = { - '@context': {'@base': 'https://schema.org/'}, - '@id': 'https://w3.org/yaml-ld/', - '@type': 'WebContent', - 'name': 'YAML-LD', - } - result = jsonld.expand(doc) - # `name` has no vocabulary-relative mapping (@vocab or term definition); - # @base must not supply one. The key is dropped. - assert result == [ - { - '@id': 'https://w3.org/yaml-ld/', - '@type': ['https://schema.org/WebContent'], - } - ] From 7a927efe678119f2b94589b0dc566e59d73c19fb Mon Sep 17 00:00:00 2001 From: Anatoly Scherbakov Date: Sun, 26 Apr 2026 19:24:43 +0400 Subject: [PATCH 6/8] Restore guiding comments around `_expand_iri` base resolution --- lib/pyld/jsonld.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/pyld/jsonld.py b/lib/pyld/jsonld.py index 30cc6c89..46c6a45e 100644 --- a/lib/pyld/jsonld.py +++ b/lib/pyld/jsonld.py @@ -5842,6 +5842,7 @@ def _expand_iri( # resolve against base rval = value + # if a base was passed and the active context has a base, use that if base is not None and '@base' in active_ctx: # the None case preserves rval as potentially relative if active_ctx['@base'] is not None: @@ -5855,6 +5856,7 @@ def _expand_iri( # we should fallback to document base elif base == '': rval = resolve(rval, DEFAULT_BASE_IRI) + # in other cases, use the base that was passed to this function elif base: rval = resolve(rval, base) From 48acf5549fa3d18f84c03421945ff17bf746f3e8 Mon Sep 17 00:00:00 2001 From: Anatoly Scherbakov Date: Sun, 26 Apr 2026 19:28:58 +0400 Subject: [PATCH 7/8] Refine testing rule for additions to existing class-based test files --- AGENTS.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index 3ba8d1ab..4ef186d5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,7 +4,8 @@ Read [CONTRIBUTING.rst](CONTRIBUTING.rst) for code style, linting (e.g. `make li ## Testing -- Prefer **function-based** pytest tests (module-level `def test_...`) over class-based tests (`class Test...`). +- When adding tests to a file that already uses class-based pytest structure (e.g. `tests/test_jsonld.py`), add them as methods on the appropriate existing class — do not introduce a parallel standalone function-based test. A repo-wide refactor to function-based tests is planned but not yet landed; until then, match each file's existing structure. +- For brand-new test files, prefer function-based pytest tests (module-level `def test_...`) over class-based tests. - Use descriptive test names that reflect behavior (e.g. `test_remote_context_via_link_alternate`). ## Committing From 3ad57a7ce7a0ef4070929e221ece2438d141b075 Mon Sep 17 00:00:00 2001 From: Anatoly Scherbakov Date: Sun, 26 Apr 2026 19:29:14 +0400 Subject: [PATCH 8/8] Add `CLAUDE.md` pointing to `AGENTS.md` for agent guidance --- CLAUDE.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..dc1d0803 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +See [AGENTS.md](AGENTS.md) for project-specific agent guidance.