diff --git a/src/pyhf/schema/__init__.py b/src/pyhf/schema/__init__.py index e527769c87..79c151b89b 100644 --- a/src/pyhf/schema/__init__.py +++ b/src/pyhf/schema/__init__.py @@ -7,9 +7,11 @@ from pyhf.schema.loader import load_schema from pyhf.schema.validator import validate from pyhf.schema import variables -from pyhf.typing import Self, SchemaVersion, Traversable +from pyhf.typing import Self, SchemaVersion, PathOrStr, Traversable from pyhf.schema.upgrader import upgrade +from pathlib import Path + __all__ = [ "load_schema", "validate", @@ -65,8 +67,7 @@ class Schema(sys.modules[__name__].__class__): # type: ignore[misc] """ - # type ignore below, see https://github.com/python/mypy/pull/11666 - def __call__(self, new_path: Traversable) -> Self: # type: ignore[valid-type] + def __call__(self, new_path: PathOrStr) -> Self: """ Change the local search path for finding schemas locally. @@ -76,7 +77,7 @@ def __call__(self, new_path: Traversable) -> Self: # type: ignore[valid-type] Returns: self (pyhf.schema.Schema): Returns itself (for contextlib management) """ - self.orig_path, variables.schemas = variables.schemas, new_path + self.orig_path, variables.schemas = variables.schemas, Path(new_path) self.orig_cache = dict(variables.SCHEMA_CACHE) variables.SCHEMA_CACHE.clear() return self @@ -95,7 +96,7 @@ def __exit__(self, *args: Any, **kwargs: Any) -> None: variables.SCHEMA_CACHE = self.orig_cache @property - def path(self) -> Traversable: + def path(self) -> Traversable | Path: """ The local path for schemas. """ diff --git a/src/pyhf/schema/validator.py b/src/pyhf/schema/validator.py index 46bdf16177..2261d89d7a 100644 --- a/src/pyhf/schema/validator.py +++ b/src/pyhf/schema/validator.py @@ -10,6 +10,14 @@ from pyhf.schema.loader import load_schema from pyhf.typing import Workspace, Model, Measurement, PatchSet from typing import Any +import sys + +# importlib.resources.as_file wasn't added until Python 3.9 +# c.f. https://docs.python.org/3.9/library/importlib.html#importlib.resources.as_file +if sys.version_info >= (3, 9): + from importlib import resources +else: + import importlib_resources as resources log = logging.getLogger(__name__) @@ -84,29 +92,36 @@ def validate( f"Specification requested version {version} but latest is {latest_known_version}. Upgrade your specification or downgrade pyhf." ) + if version is None: + msg = f'The version for {schema_name} is not set and could not be determined automatically as there is no default version specified for this schema. This could be due to using a schema that pyhf is not aware of, or a mistake.' + raise ValueError(msg) + schema = load_schema(str(Path(version).joinpath(schema_name))) - # note: trailing slash needed for RefResolver to resolve correctly and by - # design, pathlib strips trailing slashes. See ref below: - # * https://bugs.python.org/issue21039 - # * https://github.com/python/cpython/issues/65238 - resolver = jsonschema.RefResolver( - base_uri=f"{Path(variables.schemas).joinpath(version).as_uri()}/", - referrer=schema_name, - store=variables.SCHEMA_CACHE, - ) - - Validator = jsonschema.Draft202012Validator - - if allow_tensors: - type_checker = Validator.TYPE_CHECKER.redefine( - "array", _is_array_or_tensor - ).redefine("number", _is_number_or_tensor_subtype) - Validator = jsonschema.validators.extend(Validator, type_checker=type_checker) - - validator = Validator(schema, resolver=resolver, format_checker=None) - - try: - return validator.validate(spec) - except jsonschema.ValidationError as err: - raise pyhf.exceptions.InvalidSpecification(err, schema_name) # type: ignore[no-untyped-call] + with resources.as_file(variables.schemas) as path: + # note: trailing slash needed for RefResolver to resolve correctly and by + # design, pathlib strips trailing slashes. See ref below: + # * https://bugs.python.org/issue21039 + # * https://github.com/python/cpython/issues/65238 + resolver = jsonschema.RefResolver( + base_uri=f"{path.joinpath(version).as_uri()}/", + referrer=schema_name, + store=variables.SCHEMA_CACHE, + ) + + Validator = jsonschema.Draft202012Validator + + if allow_tensors: + type_checker = Validator.TYPE_CHECKER.redefine( + "array", _is_array_or_tensor + ).redefine("number", _is_number_or_tensor_subtype) + Validator = jsonschema.validators.extend( + Validator, type_checker=type_checker + ) + + validator = Validator(schema, resolver=resolver, format_checker=None) + + try: + return validator.validate(spec) + except jsonschema.ValidationError as err: + raise pyhf.exceptions.InvalidSpecification(err, schema_name) # type: ignore[no-untyped-call] diff --git a/src/pyhf/schema/variables.py b/src/pyhf/schema/variables.py index cb608e689a..fa7c8c61ad 100644 --- a/src/pyhf/schema/variables.py +++ b/src/pyhf/schema/variables.py @@ -2,13 +2,15 @@ import sys from pyhf.typing import Schema, SchemaVersion, Traversable +from pathlib import Path + # importlib.resources.as_file wasn't added until Python 3.9 # c.f. https://docs.python.org/3.9/library/importlib.html#importlib.resources.as_file if sys.version_info >= (3, 9): from importlib import resources else: import importlib_resources as resources -schemas: Traversable = resources.files('pyhf') / "schemas" +schemas: Traversable | Path = resources.files('pyhf') / "schemas" SCHEMA_CACHE: dict[str, Schema] = {} SCHEMA_BASE = "https://scikit-hep.org/pyhf/schemas/"