refactor: migrate from jsonschema.RefResolver to referencing#2716
refactor: migrate from jsonschema.RefResolver to referencing#2716henryiii wants to merge 1 commit into
Conversation
📝 WalkthroughWalkthroughThis PR migrates JSON schema $ref resolution from the deprecated jsonschema.RefResolver to the referencing library's Registry/Resource API. Dependencies are bumped, a read_schema helper is added to the loader, and validator.py is updated to build and anchor a Registry-based resolver. ChangesSchema resolution migration
Estimated code review effort: 3 (Moderate) | ~25 minutes Related issues: Suggested labels: dependencies, schema Suggested reviewers: kratsg, matthewfeickert 🐰 A resolver retired, deprecated and worn, 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #2716 +/- ##
=======================================
Coverage 98.28% 98.28%
=======================================
Files 65 65
Lines 4305 4315 +10
Branches 465 465
=======================================
+ Hits 4231 4241 +10
Misses 46 46
Partials 28 28
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
jsonschema.RefResolver has been deprecated since jsonschema 4.18. Replace it with a referencing.Registry plus a retrieve callback that loads pyhf schemas from disk, and pass registry=... to the validator instead of the deprecated resolver=... argument. Because the pyhf schemas carry a top-level $ref (e.g. model.json points at defs.json#/definitions/model), draft-06's sibling-$ref rules suppress the root $id, so the resolver base URI is set explicitly to the schema $id. A new loader.read_schema reads schemas without touching SCHEMA_CACHE so that resolving $refs does not pollute the cache (preserving existing caching behavior, including the custom schema-path context manager). Bump jsonschema>=4.18.0 and add referencing as an explicit dependency in both the project and pixi dependency tables, and drop the now-unneeded RefResolver DeprecationWarning filter. Assisted-by: ClaudeCode:claude-opus-4.8
e55e252 to
63c3f2d
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/pyhf/schema/validator.py`:
- Around line 118-125: The resolver is being patched onto the Validator after
construction via the private validator._resolver attribute, which should be
avoided. Update the Validator setup in validator.py to pass the anchored
resolver through the supported construction path using the Registry/base URI
mechanism instead of mutating the validator instance afterward. Keep the fix
centered around the Validator(...) initialization and the
registry.resolver(base_uri=schema_id) logic so relative references still resolve
correctly without relying on internals.
- Around line 41-57: In _retrieve_schema, missing referenced schemas are
currently surfaced via read_schema() errors, which causes referencing to treat
them as Unretrievable; update the retrieve callback to catch the missing-schema
case and raise referencing.exceptions.NoSuchResource instead. Keep the existing
uri-to-schema_id resolution and Resource.from_contents flow unchanged, but
ensure the missing-ref path from read_schema/SchemaNotFound maps to
NoSuchResource so the registry handles it correctly.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 20226d4d-2db8-4e0f-942d-a5c49db8f5cb
📒 Files selected for processing (3)
pyproject.tomlsrc/pyhf/schema/loader.pysrc/pyhf/schema/validator.py
| def _retrieve_schema(uri: str) -> Resource: | ||
| """ | ||
| A ``referencing`` retrieve callback that loads a pyhf schema by its URI. | ||
|
|
||
| Cross-schema ``$ref``\\ s (e.g. ``defs.json``) are resolved against the | ||
| referring schema's ``$id``. For the bundled schemas these are absolute URIs | ||
| under :data:`pyhf.schema.variables.SCHEMA_BASE`; for custom schemas (see | ||
| :class:`pyhf.schema.Schema`) they are paths relative to | ||
| :attr:`pyhf.schema.path`. In both cases stripping the base leaves the disk | ||
| path relative to :attr:`pyhf.schema.path`. | ||
|
|
||
| The schema is read directly from disk via :func:`read_schema` so that | ||
| resolving references does not pollute :data:`pyhf.schema.variables.SCHEMA_CACHE`. | ||
| """ | ||
| schema_id = uri.removeprefix(variables.SCHEMA_BASE) | ||
| schema = read_schema(schema_id) | ||
| return Resource.from_contents(schema, default_specification=DRAFT6) |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
rg -n 'SCHEMA_BASE' src/pyhf/schema/variables.py
rg -n '"\$id"' -g '*.json' src/pyhf/schema | head -n 20Repository: scikit-hep/pyhf
Length of output: 208
🌐 Web query:
referencing library retrieve callback NoSuchResource exception contract
💡 Result:
The term NoSuchResourceException (or similar variants like NoSuchResource) is not a standardized contract or built-in exception associated with the generic "retrieve callback" pattern in programming. Instead, it appears as a domain-specific exception in distinct, unrelated software libraries and services [1][2][3][4]. In the context of the popular framework LangChain, which heavily utilizes retrieval and callback patterns, there is no generic NoSuchResource exception defined for retriever callbacks [5][6][7]. LangChain's retriever callbacks (such as CallbackManagerForRetrieverRun) utilize methods like on_retriever_end and on_retriever_error to manage lifecycle events [5]. If a retrieval operation fails, it generally catches generic Exception types and reports them via on_retriever_error [6]. Outside of generic patterns, NoSuchResourceException is explicitly found in: 1. AWS SDKs: A specific exception used by the Service Quotas service to indicate that a requested resource does not exist [2][4]. 2. Rust Libraries: Defined in crates like resources and buck_resources to signal that a specific resource could not be found within a container [1][3][8]. 3. Other APIs: Historical or domain-specific APIs, such as the Geneious Public API, use RetrieveCallback interfaces, but these also do not establish a universal contract involving a NoSuchResource exception [9]. If you are encountering this error, it is likely being raised by a specific underlying service (such as an AWS API call) or a custom implementation within your dependency tree, rather than as part of a standard interface contract for callback retrieval [2][4].
Citations:
- 1: https://docs.rs/resources/latest/resources/struct.NoSuchResource.html
- 2: https://docs.aws.amazon.com/botocore/latest/reference/services/service-quotas/client/exceptions/NoSuchResourceException.html
- 3: https://docs.rs/buck-resources/latest/buck_resources/enum.BuckResourcesError.html
- 4: https://sdk.amazonaws.com/java/api/2.41.1/software/amazon/awssdk/services/servicequotas/model/NoSuchResourceException.html
- 5: https://fossies.org/dox/langchain-langchain-core-1.4.0/classlangchain__core_1_1callbacks_1_1manager_1_1CallbackManagerForRetrieverRun.html
- 6: https://github.com/langchain-ai/langchain/blob/22219eefaff48e869327e24e8f3fcde52781d03f/libs/core/langchain_core/retrievers.py
- 7: https://stackoverflow.com/questions/77156069/using-langchain-with-custom-retrievers
- 8: https://docs.rs/resources/latest/resources/enum.CantGetResource.html
- 9: https://assets.geneious.com/developer/geneious/javadoc/8.0.0/com/biomatters/geneious/publicapi/databaseservice/RetrieveCallback.html
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== validator.py ==\n'
cat -n src/pyhf/schema/validator.py | sed -n '1,140p'
printf '\n== variables.py ==\n'
cat -n src/pyhf/schema/variables.py | sed -n '1,120p'
printf '\n== schema loader references ==\n'
rg -n "def read_schema|SchemaNotFound|NoSuchResource|retrieve" src/pyhf/schema src/pyhf -g '!**/__pycache__/**'
printf '\n== dependency pins ==\n'
rg -n "referencing|referencing==" pyproject.toml poetry.lock requirements*.txt setup.cfg setup.py uv.lock pdm.lock package-lock.json 2>/dev/null || trueRepository: scikit-hep/pyhf
Length of output: 8809
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
for p in [Path('src/pyhf/schema/validator.py'), Path('src/pyhf/schema/variables.py')]:
print(f"\n== {p} ==")
text = p.read_text().splitlines()
for i, line in enumerate(text, 1):
if i <= 220:
print(f"{i}:{line}")
PYRepository: scikit-hep/pyhf
Length of output: 5790
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== schema files ==\n'
find src/pyhf/schemas -name '*.json' -maxdepth 3 | sort
printf '\n== $id values ==\n'
python3 - <<'PY'
import json
from pathlib import Path
for p in sorted(Path('src/pyhf/schemas').rglob('*.json')):
data = json.loads(p.read_text())
print(f"{p}: {data.get('$id')}")
PY
printf '\n== loader.py ==\n'
cat -n src/pyhf/schema/loader.py | sed -n '1,220p'
printf '\n== referencing package location ==\n'
python3 - <<'PY'
import referencing, inspect
from referencing import Registry
print(referencing.__file__)
print(inspect.getsourcefile(Registry))
PYRepository: scikit-hep/pyhf
Length of output: 4062
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
import json
import traceback
from referencing import Registry
from referencing.jsonschema import DRAFT6
def probe(exc):
calls = []
def retrieve(uri):
calls.append(uri)
raise exc(f"missing: {uri}")
reg = Registry(retrieve=retrieve)
resource = reg.get("https://example.com/schema.json")
print("Registry.get missing resource ->", resource)
try:
reg.contents("https://example.com/schema.json")
except Exception as e:
print(type(e).__name__, str(e))
print("is exc?", isinstance(e, type(exc("x"))))
else:
print("contents unexpectedly succeeded")
for exc in [FileNotFoundError]:
probe(exc)
print("\n-- search schema ids --")
for p in sorted(Path("src/pyhf/schemas").rglob("*.json")):
data = json.loads(p.read_text())
if "$id" in data:
print(p, "=>", data["$id"])
PYRepository: scikit-hep/pyhf
Length of output: 851
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
import inspect
import referencing
from referencing import Registry
print("referencing module:", referencing.__file__)
print("Registry source file:", inspect.getsourcefile(Registry))
PY
python3 - <<'PY'
import inspect
import referencing
from pathlib import Path
pkg = Path(referencing.__file__).resolve().parent
print(pkg)
for path in pkg.rglob("*.py"):
text = path.read_text(errors="ignore")
if "NoSuchResource" in text or "retrieve" in text and "Registry" in text:
print("\nFILE:", path)
for i, line in enumerate(text.splitlines(), 1):
if "NoSuchResource" in line or "retrieve" in line or "FileNotFoundError" in line:
print(f"{i}:{line}")
PYRepository: scikit-hep/pyhf
Length of output: 8469
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
import json
from pathlib import Path
from referencing import Registry
print("== missing-resource probe ==")
calls = []
def retrieve(uri):
calls.append(uri)
raise FileNotFoundError(f"missing: {uri}")
reg = Registry(retrieve=retrieve)
try:
# Try to force retrieval through registry APIs if available.
for method_name, args in [
("get", ("https://example.com/schema.json",)),
("contents", ("https://example.com/schema.json",)),
("resolver", tuple()),
]:
if hasattr(reg, method_name):
method = getattr(reg, method_name)
try:
result = method(*args)
print(method_name, "=>", type(result).__name__, result)
except Exception as e:
print(method_name, "raised", type(e).__name__, str(e))
except Exception as e:
print("unexpected", type(e).__name__, str(e))
print("retrieve_calls:", calls)
print("\n== schema ids ==")
for p in sorted(Path("src/pyhf/schemas").rglob("*.json")):
data = json.loads(p.read_text())
print(f"{p}: {data.get('$id')}")
PYRepository: scikit-hep/pyhf
Length of output: 945
🌐 Web query:
referencing 0.28.4 retrieve callback FileNotFoundError NoSuchResource documentation
💡 Result:
The terms "FileNotFoundError", "NoSuchResource", "retrieve", and "callback" appear across several distinct software contexts, as no single library documentation explicitly links these four terms within version "0.28.4" [1][2]. 1. LangChain: In the LangChain ecosystem, particularly within langchain_core, retrievers utilize callback managers (e.g., CallbackManagerForRetrieverRun) which define on_retriever_error methods to handle exceptions occurring during the retrieval process [3][4][5]. While these components manage errors during document retrieval, they do not specifically use a "NoSuchResource" exception as a primary, universal identifier [6]. 2. AWS / Boto3 / Botocore: "NoSuchResourceException" is a specific exception type in AWS services (e.g., Service Quotas) [7], and "NoSuchKey" is used in S3 [8]. Developers frequently encounter FileNotFoundError when using boto3's download_file method if the local path is invalid or if the underlying transfer process fails to locate resources [9]. 3. OpenAI Assistants API: Historically, users encountered openai.error.FileNotFoundError when the retrieval tool could not find a file ID [10]. However, this was largely deprecated and replaced in newer versions of the OpenAI library, with file management now handled via tool_resources [10]. 4. Version 0.28.4: Documentation or changelogs for this version number typically refer to specific project updates, such as Plotly Dash 0.28.4 (which addressed callback exception handling) [1] or other smaller libraries [2], rather than a unified standard linking the requested error and retrieval terms. If you are encountering this error, it is likely specific to the library you are currently implementing. It is recommended to check the stack trace to determine which library (e.g., boto3, langchain, or an API client) is raising the exception.
Citations:
- 1: https://raw.githubusercontent.com/plotly/dash/3541ee4991a5e7bbe618c0212b69525bc102e3ee/CHANGELOG.md
- 2: https://git.inmo.dev/InsanusMokrassar/tgbotapi/compare/v31.2.0...0.38.2
- 3: https://github.com/langchain-ai/langchain/discussions/16033
- 4: https://sj-langchain.readthedocs.io/en/latest/callbacks/langchain.callbacks.manager.CallbackManagerForRetrieverRun.html
- 5: https://fossies.org/dox/langchain-langchain-core-1.4.0/classlangchain__core_1_1callbacks_1_1manager_1_1CallbackManagerForRetrieverRun.html
- 6: https://github.com/langchain-ai/langchain/blob/langchain==0.2.17/libs/core/langchain_core/retrievers.py
- 7: https://docs.aws.amazon.com/botocore/latest/reference/services/service-quotas/client/exceptions/NoSuchResourceException.html
- 8: https://docs.aws.amazon.com/botocore/latest/reference/services/s3/client/exceptions/NoSuchKey.html
- 9: [Bug Report] FileNotFoundError when downloading dataset aws/amazon-sagemaker-examples#3111
- 10: https://theneuralbase.com/openai-assistants/errors/openai-file-not-found-retrieval-tool-error/
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== referencing get_or_retrieve ==\n'
python3 - <<'PY'
from pathlib import Path
import inspect
import referencing._core as core
path = Path(inspect.getsourcefile(core))
lines = path.read_text().splitlines()
for start in [410, 420, 428, 438, 445]:
a = max(1, start-8)
b = min(len(lines), start+20)
print(f"\n-- {path}:{a}-{b} --")
for i in range(a, b+1):
print(f"{i}:{lines[i-1]}")
PY
printf '\n== SchemaNotFound usage ==\n'
rg -n "SchemaNotFound" src/pyhf -g '!**/__pycache__/**'Repository: scikit-hep/pyhf
Length of output: 6938
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n "Unretrievable|NoSuchResource|SchemaNotFound|InvalidSpecification" src/pyhf -g '!**/__pycache__/**'Repository: scikit-hep/pyhf
Length of output: 1344
Raise NoSuchResource from _retrieve_schema. read_schema() raises SchemaNotFound (FileNotFoundError) for a missing ref, and referencing wraps non-NoSuchResource errors as Unretrievable. Raising referencing.exceptions.NoSuchResource here keeps missing-schema refs on the registry’s expected path.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/pyhf/schema/validator.py` around lines 41 - 57, In _retrieve_schema,
missing referenced schemas are currently surfaced via read_schema() errors,
which causes referencing to treat them as Unretrievable; update the retrieve
callback to catch the missing-schema case and raise
referencing.exceptions.NoSuchResource instead. Keep the existing
uri-to-schema_id resolution and Resource.from_contents flow unchanged, but
ensure the missing-ref path from read_schema/SchemaNotFound maps to
NoSuchResource so the registry handles it correctly.
| validator = Validator(schema, registry=registry, format_checker=None) | ||
|
|
||
| # The pyhf schemas carry a top-level ``$ref`` (e.g. ``model.json`` points at | ||
| # ``defs.json#/definitions/model``). Under draft-06 a sibling ``$ref`` | ||
| # suppresses ``$id``, so jsonschema cannot infer the base URI of the root | ||
| # schema and relative references like ``defs.json`` fail to resolve. Anchor | ||
| # the resolver at the schema ``$id`` so those references resolve correctly. | ||
| validator._resolver = registry.resolver(base_uri=schema_id) |
There was a problem hiding this comment.
🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
jsonschema referencing set base uri validator registry resolver supported API 4.18
💡 Result:
In the jsonschema Python library version 4.18, the legacy RefResolver has been deprecated in favor of a newer referencing-based API [1]. The referencing package now handles the registry and resolution of JSON schemas [2][1]. Instead of using RefResolver, you should use the referencing.Registry object [2][3]. Key changes and concepts in the 4.18+ API include: 1. Deprecation of RefResolver: The _RefResolver class, formerly used for resolving $ref properties and managing base URIs, is deprecated [1]. 2. Introducing Registry: You should now create a referencing.Registry object, which acts as an immutable set of JSON schemas that can be either in-memory or retrieved externally [2]. 3. Configuring Validators: When instantiating a validator (e.g., Draft202012Validator), you pass the registry directly via the registry keyword argument rather than using the resolver argument [4][2][3]. 4. Base URI and Identification: Schemas are identified by URIs, often via the $id keyword [2][5]. The Registry allows you to explicitly associate a URI with a specific schema resource using with_resource or similar methods, which effectively handles resolution without needing a manual resolver instance [2][3]. Example Migration Pattern: - Old approach: DraftNValidator(..., resolver=_RefResolver(...)) - New approach: DraftNValidator(..., registry=Registry.with_resources(...)) [2] For more advanced customization of resolution behavior, refer to the referencing library documentation, which defines how references resolve against a schema's base URI [2][3].
Citations:
- 1: https://python-jsonschema.readthedocs.io/en/v4.18.2/api/jsonschema/protocols/
- 2: https://python-jsonschema.readthedocs.io/en/v4.18.4/referencing/
- 3: https://python-jsonschema.readthedocs.io/en/v4.18.2/referencing/
- 4: https://python-jsonschema.readthedocs.io/en/v4.18.6/api/jsonschema/validators/
- 5: https://www.mintlify.com/json-schema-org/json-schema-spec/reference/core/id
🏁 Script executed:
python3 - <<'PY'
from pathlib import Path
import re
paths = [
"src/pyhf/schema/validator.py",
"pyproject.toml",
"setup.py",
"requirements.txt",
"requirements-dev.txt",
"poetry.lock",
"uv.lock",
]
for p in paths:
path = Path(p)
if path.exists():
print(f"== {p} ==")
text = path.read_text()
if p.endswith("validator.py"):
for i, line in enumerate(text.splitlines(), 1):
if 1 <= i <= 220:
print(f"{i:4}: {line}")
else:
for m in re.finditer(r"jsonschema[^\\n]*", text, flags=re.I):
print(m.group(0))
PYRepository: scikit-hep/pyhf
Length of output: 6092
🌐 Web query:
jsonschema 4.18 validator _resolver registry.resolver(base_uri) public API Resource root $id draft-06 sibling $ref docs
💡 Result:
In jsonschema version 4.18.0 and later, the _RefResolver class and its associated mechanisms (such as accessing a validator's.resolver property) are deprecated in favor of the referencing library [1][2][3]. The internal _resolver attribute and registry mechanism are not intended for public API use. Instead, schema referencing and resolution should be managed using the referencing.Registry and related objects [1][4]. Regarding the specific behaviors mentioned: 1. Deprecation: As of v4.18.0, Validator.resolver is deprecated [3][5]. The library has moved to the referencing library, which provides more compliant and flexible referencing behavior [3][4]. 2. Registry and Resolver: When a validator is initialized, it internally manages a registry and resolver [3]. If you need to customize how references are resolved, you should pass a custom referencing.Registry instance to the validator constructor rather than attempting to manipulate private internal resolver attributes [1][4]. 3. Draft-06 Sibling $ref Behavior: In JSON Schema Draft 6, the $ref keyword effectively ignores any sibling keywords within the same subschema object [6][7]. This is a specified behavior of the JSON Schema core specification for older drafts, not a quirk of the jsonschema library's implementation [6][8]. To include additional keywords alongside a $ref, you must wrap them in a construct like allOf [6]. 4. $id and URI Resolution: The $id keyword defines the base URI for relative URI references [6][9]. The jsonschema library resolves relative references against the current base URI, which is determined by the closest parent $id or the initial document context [6][9]. If you rely on old behaviors (such as automatic remote resolution, which was supported by the deprecated _RefResolver), the new referencing.Registry does not perform automatic network retrieval by default for security reasons [1][4]. You must explicitly provide a retrieve function to the registry if you require that functionality [1][4].
Citations:
- 1: https://python-jsonschema.readthedocs.io/en/v4.18.4/referencing/
- 2: https://python-jsonschema.readthedocs.io/en/v4.18.2/api/jsonschema/protocols/
- 3: https://python-jsonschema.readthedocs.io/en/v4.18.4/_modules/jsonschema/validators/
- 4: https://python-jsonschema.readthedocs.io/en/stable/referencing/
- 5: https://python-jsonschema.readthedocs.io/en/v4.23.0/_modules/jsonschema/validators/
- 6: https://www.learnjsonschema.com/draft6/core/ref/
- 7: https://python-jsonschema.readthedocs.io/en/latest/_modules/jsonschema/validators/
- 8: https://json-schema.org/draft-06/json-schema-release-notes
- 9: https://www.learnjsonschema.com/draft6/core/id/
🌐 Web query:
python-jsonschema 4.18 validator constructor base_uri registry resolver resolver(base_uri) draft6 resource with_resource
💡 Result:
In python-jsonschema version 4.18.0 and later, the _RefResolver class and the resolver argument in validator constructors are deprecated in favor of the referencing library's Registry object [1][2][3]. Registry and with_resource The modern approach for managing JSON Schema references is to use referencing.Registry [1][4]. To add resources to a registry, you use the with_resource method [5][4]. This method associates a URI with a specific schema resource [4]. Example usage: from referencing import Registry from referencing.jsonschema import DRAFT202012 from jsonschema import Draft202012Validator # Create a resource using the appropriate specification resource = DRAFT202012.create_resource({"type": "integer"}) # Use with_resource to add it to a Registry registry = Registry.with_resource(uri="http://example.com/schema", resource=resource) # Pass the registry to the validator validator = Draft202012Validator( {"$ref": "http://example.com/schema"}, registry=registry,) Validator Constructor When instantiating a validator (e.g., Draft202012Validator), you now pass a registry argument instead of a resolver [1][6][2]. If no registry is provided, the validator creates one internally [6][2]. The legacy resolver argument remains for backward compatibility but emits a DeprecationWarning, as the library now delegates referencing behavior to the referencing library [2][3][7]. Key Changes - Deprecation: _RefResolver is deprecated [1][2]. - Replacement: Use referencing.Registry [1][5]. - Configuration: Pass registry=... to the validator constructor instead of resolver=... [1][4]. - Resource Creation: Use the create_resource method from the specific JSON Schema draft (e.g., DRAFT202012.create_resource) to prepare schemas for the registry [1][4].
Citations:
- 1: https://python-jsonschema.readthedocs.io/en/v4.18.4/referencing/
- 2: https://python-jsonschema.readthedocs.io/en/v4.18.2/api/jsonschema/protocols/
- 3: https://python-jsonschema.readthedocs.io/en/v4.18.6/_modules/jsonschema/validators/
- 4: https://python-jsonschema.readthedocs.io/en/v4.25.1/referencing/
- 5: https://python-jsonschema.readthedocs.io/en/stable/referencing/
- 6: https://python-jsonschema.readthedocs.io/en/v4.18.3/validate/
- 7: https://python-jsonschema.readthedocs.io/en/v4.18.0/_modules/jsonschema/validators/
🏁 Script executed:
python3 - <<'PY'
import inspect
import jsonschema
from jsonschema import validators
from referencing import Registry
from referencing.jsonschema import DRAFT6
print("jsonschema", jsonschema.__version__)
print("Draft6Validator signature:", inspect.signature(jsonschema.Draft6Validator))
print("Validator init source excerpt:")
src = inspect.getsource(jsonschema.Draft6Validator.__init__)
print(src)
print("Registry methods:", [m for m in dir(Registry) if m in {"resolver","with_resource","with_contents","with_resources"}])
print("Registry.resolver signature:", inspect.signature(Registry.resolver))
PYRepository: scikit-hep/pyhf
Length of output: 1195
🏁 Script executed:
python3 - <<'PY'
import inspect
from jsonschema import Draft6Validator
import jsonschema.validators as v
print("Draft6Validator.__init__ source:")
print(inspect.getsource(Draft6Validator.__init__))
print("\nWhere _resolver is used in validators.py:")
src = inspect.getsource(v)
for needle in ["_resolver", "resolver=", "Registry("]:
print(f"\n-- {needle} --")
for line in src.splitlines():
if needle in line:
print(line)
PYRepository: scikit-hep/pyhf
Length of output: 1917
Pass the anchored resolver at construction, not via validator._resolver. jsonschema exposes base-URI resolution on Registry, but the validator hook is private; threading the resolver into init avoids a post-init patch and keeps this from depending on internals.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/pyhf/schema/validator.py` around lines 118 - 125, The resolver is being
patched onto the Validator after construction via the private
validator._resolver attribute, which should be avoided. Update the Validator
setup in validator.py to pass the anchored resolver through the supported
construction path using the Registry/base URI mechanism instead of mutating the
validator instance afterward. Keep the fix centered around the Validator(...)
initialization and the registry.resolver(base_uri=schema_id) logic so relative
references still resolve correctly without relying on internals.
🤖 AI text below 🤖
Part of the code review in #2706.
Resolves #2139.
Summary
jsonschema.RefResolverhas been deprecated since jsonschema 4.18. This migrates pyhf's schema validation to thereferencinglibrary:src/pyhf/schema/validator.pynow builds areferencing.Registrywith aretrievecallback (_retrieve_schema) that loads referenced schemas from disk, and passesregistry=...to thejsonschemavalidator instead of the deprecatedresolver=....$ref(e.g.model.json->defs.json#/definitions/model). Under draft-06, a sibling$refsuppresses$id, so jsonschema cannot infer the root schema's base URI. The resolver base URI is therefore anchored explicitly to the schema$idso relative references likedefs.jsonresolve correctly.loader.read_schemareads a schema directly from disk without consulting or mutatingSCHEMA_CACHE, so resolving$refs does not pollute the cache. This preserves the existing caching semantics, including thepyhf.schema(new_path)custom-schema-path context manager and theInvalidSpecificationexception behavior.jsonschema>=4.15.0->jsonschema>=4.18.0and addedreferencing>=0.28.4as an explicit dependency in both the project and pixi dependency tables (it is now imported directly).RefResolverDeprecationWarningfilter. Since pytest runs withfilterwarnings=error, any remaining deprecation path would now fail the suite.Test plan
uv pip install --group dev --editable .pytest tests/test_schema.py(70 passed)pytest tests/test_workspace.py tests/test_pdf.py(308 passed, 6 skipped)pytest tests/test_validation.py tests/test_patchset.py tests/test_public_api.py(all pass)pytest --doctest-modules src/pyhf/schema/(3 passed)prek -a(lint clean)🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
$refhandling.Bug Fixes