From 4934ed936b76d824718beaf95875c4256609652b Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Thu, 1 Sep 2022 11:22:45 -0700 Subject: [PATCH 01/39] update validator to latest draft --- src/pyhf/schema/validator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyhf/schema/validator.py b/src/pyhf/schema/validator.py index 2540a3d002..96e85bc5f4 100644 --- a/src/pyhf/schema/validator.py +++ b/src/pyhf/schema/validator.py @@ -83,7 +83,7 @@ def validate( store=variables.SCHEMA_CACHE, ) - Validator = jsonschema.Draft6Validator + Validator = jsonschema.Draft202012Validator if allow_tensors: type_checker = Validator.TYPE_CHECKER.redefine( From 2569988f6c5a839b758a1e029bfa74eb633b34b5 Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Thu, 1 Sep 2022 11:26:15 -0700 Subject: [PATCH 02/39] add v1.0.1 --- src/pyhf/patchset.py | 4 +- src/pyhf/schema/variables.py | 2 +- src/pyhf/schemas/1.0.1/defs.json | 315 ++++++++++++++++++++++++ src/pyhf/schemas/1.0.1/jsonpatch.json | 5 + src/pyhf/schemas/1.0.1/measurement.json | 5 + src/pyhf/schemas/1.0.1/model.json | 5 + src/pyhf/schemas/1.0.1/patchset.json | 5 + src/pyhf/schemas/1.0.1/workspace.json | 5 + 8 files changed, 343 insertions(+), 3 deletions(-) create mode 100644 src/pyhf/schemas/1.0.1/defs.json create mode 100644 src/pyhf/schemas/1.0.1/jsonpatch.json create mode 100644 src/pyhf/schemas/1.0.1/measurement.json create mode 100644 src/pyhf/schemas/1.0.1/model.json create mode 100644 src/pyhf/schemas/1.0.1/patchset.json create mode 100644 src/pyhf/schemas/1.0.1/workspace.json diff --git a/src/pyhf/patchset.py b/src/pyhf/patchset.py index b72118835b..605aa8ce34 100644 --- a/src/pyhf/patchset.py +++ b/src/pyhf/patchset.py @@ -117,11 +117,11 @@ class PatchSet: ... ] ... } ... ], - ... "version": "1.0.0" + ... "version": "1.0.1" ... }) ... >>> patchset.version - '1.0.0' + '1.0.1' >>> patchset.references {'hepdata': 'ins1234567'} >>> patchset.description diff --git a/src/pyhf/schema/variables.py b/src/pyhf/schema/variables.py index 80c0a0dd06..2f7c1d92ea 100644 --- a/src/pyhf/schema/variables.py +++ b/src/pyhf/schema/variables.py @@ -10,4 +10,4 @@ SCHEMA_CACHE = {} SCHEMA_BASE = "https://scikit-hep.org/pyhf/schemas/" -SCHEMA_VERSION = '1.0.0' +SCHEMA_VERSION = '1.0.1' diff --git a/src/pyhf/schemas/1.0.1/defs.json b/src/pyhf/schemas/1.0.1/defs.json new file mode 100644 index 0000000000..347975088c --- /dev/null +++ b/src/pyhf/schemas/1.0.1/defs.json @@ -0,0 +1,315 @@ +{ + "$schema": "http://json-schema.org/draft/2020-12/schema#", + "$id": "https://scikit-hep.org/pyhf/schemas/1.0.1/defs.json", + "definitions": { + "workspace": { + "type": "object", + "properties": { + "channels": { "type": "array", "items": {"$ref": "#/definitions/channel"}, "minItems": 1 }, + "measurements": { "type": "array", "items": {"$ref": "#/definitions/measurement"}, "minItems": 1 }, + "observations": { "type": "array", "items": {"$ref": "#/definitions/observation" }, "minItems": 1 }, + "version": { "const": "1.0.1" } + }, + "additionalProperties": false, + "required": ["channels", "measurements", "observations", "version"] + }, + "model": { + "type": "object", + "properties": { + "channels": { "type": "array", "items": {"$ref": "#/definitions/channel"}, "minItems": 1 }, + "parameters": { "type": "array", "items": {"$ref": "#/definitions/parameter"} } + }, + "additionalProperties": false, + "required": ["channels"] + }, + "observation": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "data": { "type": "array", "items": {"type": "number"}, "minItems": 1 } + }, + "required": ["name", "data"], + "additionalProperties": false + }, + "measurement": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "config": { "$ref": "#/definitions/config" } + }, + "required": ["name", "config"], + "additionalProperties": false + }, + "config": { + "type": "object", + "properties": { + "poi": { "type" : "string" }, + "parameters": { "type": "array", "items": {"$ref": "#/definitions/parameter"} } + }, + "required": ["poi", "parameters"], + "additionalProperties": false + }, + "parameter": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "inits": { "type": "array", "items": {"type": "number"}, "minItems": 1 }, + "bounds": { "type": "array", "items": {"type": "array", "items": {"type": "number", "minItems": 2, "maxItems": 2}}, "minItems": 1 }, + "auxdata": { "type": "array", "items": {"type": "number"}, "minItems": 1 }, + "factors": { "type": "array", "items": {"type": "number"}, "minItems": 1 }, + "sigmas": { "type": "array", "items": {"type": "number"}, "minItems": 1}, + "fixed": { "type": "boolean" } + }, + "required": ["name"], + "additionalProperties": false + }, + "channel": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "samples": { "type": "array", "items": {"$ref": "#/definitions/sample"}, "minItems": 1 } + }, + "required": ["name", "samples"], + "additionalProperties": false + }, + "sample": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "data": { "type": "array", "items": {"type": "number"}, "minItems": 1 }, + "modifiers": { + "type": "array", + "items": { + "anyOf": [ + { "$ref": "#/definitions/modifier/histosys" }, + { "$ref": "#/definitions/modifier/lumi" }, + { "$ref": "#/definitions/modifier/normfactor" }, + { "$ref": "#/definitions/modifier/normsys" }, + { "$ref": "#/definitions/modifier/shapefactor" }, + { "$ref": "#/definitions/modifier/shapesys" }, + { "$ref": "#/definitions/modifier/staterror" } + ] + } + } + }, + "required": ["name", "data", "modifiers"], + "additionalProperties": false + }, + "modifier": { + "histosys": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "type": { "const": "histosys" }, + "data": { + "type": "object", + "properties": { + "lo_data": { "type": "array", "items": {"type": "number"}, "minItems": 1 }, + "hi_data": { "type": "array", "items": {"type": "number"}, "minItems": 1 } + }, + "required": ["lo_data", "hi_data"], + "additionalProperties": false + } + }, + "required": ["name", "type", "data"], + "additionalProperties": false + }, + "lumi": { + "type": "object", + "properties": { + "name": { "const": "lumi" }, + "type": { "const": "lumi" }, + "data": { "type": "null" } + }, + "required": ["name", "type", "data"], + "additionalProperties": false + }, + "normfactor": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "type": { "const": "normfactor" }, + "data": { "type": "null" } + }, + "required": ["name", "type", "data"], + "additionalProperties": false + }, + "normsys": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "type": { "const": "normsys" }, + "data": { + "type": "object", + "properties": { + "lo": { "type": "number" }, + "hi": { "type": "number"} + }, + "required": ["lo", "hi"], + "additionalProperties": false + } + }, + "required": ["name", "type", "data"], + "additionalProperties": false + }, + "shapefactor": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "type": { "const": "shapefactor" }, + "data": { "type": "null" } + }, + "required": ["name", "type", "data"], + "additionalProperties": false + }, + "shapesys": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "type": { "const": "shapesys" }, + "data": { "type": "array", "items": {"type": "number"}, "minItems": 1 } + }, + "required": ["name", "type", "data"], + "additionalProperties": false + }, + "staterror": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "type": { "const": "staterror" }, + "data": { "type": "array", "items": {"type": "number"}, "minItems": 1 } + }, + "required": ["name", "type", "data"], + "additionalProperties": false + } + }, + "jsonpatch": { + "description": "an array of patch operations (copied from http://json.schemastore.org/json-patch)", + "type": "array", + "items": { + "$ref": "#/definitions/jsonpatch/operation" + }, + "operation": { + "type": "object", + "required": [ "op", "path" ], + "allOf": [ { "$ref": "#/definitions/jsonpatch/path" } ], + "oneOf": [ + { + "required": [ "value" ], + "properties": { + "op": { + "description": "The operation to perform.", + "type": "string", + "enum": [ "add", "replace", "test" ] + }, + "value": { + "description": "The value to add, replace or test." + } + } + }, + { + "properties": { + "op": { + "description": "The operation to perform.", + "type": "string", + "enum": [ "remove" ] + } + } + }, + { + "required": [ "from" ], + "properties": { + "op": { + "description": "The operation to perform.", + "type": "string", + "enum": [ "move", "copy" ] + }, + "from": { + "description": "A JSON Pointer path pointing to the location to move/copy from.", + "type": "string" + } + } + } + ] + }, + "path": { + "properties": { + "path": { + "description": "A JSON Pointer path.", + "type": "string" + } + } + } + }, + "patchset": { + "description": "A set of JSONPatch patches which modify a pyhf workspace", + "type": "object", + "properties": { + "patches": { "$ref": "#/definitions/patchset/patches" }, + "metadata": { "$ref": "#/definitions/patchset/metadata" }, + "version": { "const": "1.0.1" } + }, + "additionalProperties": false, + "required": ["patches", "metadata", "version"], + "references": { + "type": "object", + "properties": { + "hepdata": { "type": "string", "pattern": "^ins[0-9]{7}$" } + }, + "additionalProperties": false, + "minProperties": 1 + }, + "digests": { + "type": "object", + "properties": { + "md5": { "type": "string", "pattern": "^[a-f0-9]{32}$" }, + "sha256": { "type": "string", "pattern": "^[a-fA-F0-9]{64}$" } + }, + "additionalProperties": false, + "minProperties": 1 + }, + "patches": { + "type": "array", + "items": { "$ref": "#/definitions/patchset/patch" }, + "minItems": 1 + }, + "patch": { + "type": "object", + "properties": { + "patch": { "$ref": "#/definitions/jsonpatch" }, + "metadata": { + "type": "object", + "properties": { + "name": { "type": "string", "pattern": "^[a-zA-Z0-9_]+$" }, + "values": { + "type": "array", + "items": { + "anyOf": [{"type": "number"}, {"type": "string"}] + } + } + }, + "required": ["name", "values"], + "additionalProperties": true + } + }, + "required": ["metadata", "patch"], + "additionalProperties": false + }, + "metadata": { + "type": "object", + "properties": { + "digests": { "$ref": "#/definitions/patchset/digests" }, + "labels": { + "type": "array", + "items": { "type": "string", "pattern": "^[a-zA-Z0-9_]+$" }, + "minItems": 1 + }, + "description": { "type": "string" }, + "references": { "$ref": "#/definitions/patchset/references" } + }, + "required": ["references", "digests", "labels", "description"], + "additionalProperties": true + } + } + } +} diff --git a/src/pyhf/schemas/1.0.1/jsonpatch.json b/src/pyhf/schemas/1.0.1/jsonpatch.json new file mode 100644 index 0000000000..c5886eb6ba --- /dev/null +++ b/src/pyhf/schemas/1.0.1/jsonpatch.json @@ -0,0 +1,5 @@ +{ + "$schema": "http://json-schema.org/draft/2020-12/schema#", + "$id": "https://scikit-hep.org/pyhf/schemas/1.0.1/jsonpatch.json", + "$ref": "defs.json#/definitions/jsonpatch" +} diff --git a/src/pyhf/schemas/1.0.1/measurement.json b/src/pyhf/schemas/1.0.1/measurement.json new file mode 100644 index 0000000000..d7da3dddd6 --- /dev/null +++ b/src/pyhf/schemas/1.0.1/measurement.json @@ -0,0 +1,5 @@ +{ + "$schema": "http://json-schema.org/draft/2020-12/schema#", + "$id": "https://scikit-hep.org/pyhf/schemas/1.0.1/measurement.json", + "$ref": "defs.json#/definitions/measurement" +} diff --git a/src/pyhf/schemas/1.0.1/model.json b/src/pyhf/schemas/1.0.1/model.json new file mode 100644 index 0000000000..4b9a8f15d8 --- /dev/null +++ b/src/pyhf/schemas/1.0.1/model.json @@ -0,0 +1,5 @@ +{ + "$schema": "http://json-schema.org/draft/2020-12/schema#", + "$id": "https://scikit-hep.org/pyhf/schemas/1.0.1/model.json", + "$ref": "defs.json#/definitions/model" +} diff --git a/src/pyhf/schemas/1.0.1/patchset.json b/src/pyhf/schemas/1.0.1/patchset.json new file mode 100644 index 0000000000..0f0bacc5ca --- /dev/null +++ b/src/pyhf/schemas/1.0.1/patchset.json @@ -0,0 +1,5 @@ +{ + "$schema": "http://json-schema.org/draft/2020-12/schema#", + "$id": "https://scikit-hep.org/pyhf/schemas/1.0.1/patchset.json", + "$ref": "defs.json#/definitions/patchset" +} diff --git a/src/pyhf/schemas/1.0.1/workspace.json b/src/pyhf/schemas/1.0.1/workspace.json new file mode 100644 index 0000000000..200d3ba58e --- /dev/null +++ b/src/pyhf/schemas/1.0.1/workspace.json @@ -0,0 +1,5 @@ +{ + "$schema": "http://json-schema.org/draft/2020-12/schema#", + "$id": "https://scikit-hep.org/pyhf/schemas/1.0.1/workspace.json", + "$ref": "defs.json#/definitions/workspace" +} From bb7b416b500bcf800d91b7d81459d6d663ff0522 Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Thu, 1 Sep 2022 11:34:04 -0700 Subject: [PATCH 03/39] move load_schema for defs.json as referrer document to ensure we always have it --- src/pyhf/schema/loader.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/pyhf/schema/loader.py b/src/pyhf/schema/loader.py index 920766c4dc..7fb25f509e 100644 --- a/src/pyhf/schema/loader.py +++ b/src/pyhf/schema/loader.py @@ -54,9 +54,3 @@ def load_schema(schema_id: str): schema = json.load(json_schema) variables.SCHEMA_CACHE[schema['$id']] = schema return variables.SCHEMA_CACHE[schema['$id']] - - -# pre-populate the cache to avoid network access -# on first validation in standard usage -# (not in pyhf.schema.variables to avoid circular imports) -load_schema(f'{variables.SCHEMA_VERSION}/defs.json') From 3dd57661126aef1c9f1d3a89fa34b0c78644a218 Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Thu, 1 Sep 2022 11:45:14 -0700 Subject: [PATCH 04/39] add version to typing --- src/pyhf/typing.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pyhf/typing.py b/src/pyhf/typing.py index f012e3af22..acaacce380 100644 --- a/src/pyhf/typing.py +++ b/src/pyhf/typing.py @@ -143,6 +143,7 @@ class Workspace(TypedDict): measurements: Sequence[Measurement] channels: Sequence[Channel] observations: Sequence[Observation] + version: Literal['1.0.1', '1.0.0'] class TensorBackend(Protocol): From 81c58163c998cf563a7e7f55b27c5b87037a8a5f Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Thu, 1 Sep 2022 11:49:32 -0700 Subject: [PATCH 05/39] add warning when version is wrong --- src/pyhf/schema/validator.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/pyhf/schema/validator.py b/src/pyhf/schema/validator.py index 96e85bc5f4..3334d7b4e3 100644 --- a/src/pyhf/schema/validator.py +++ b/src/pyhf/schema/validator.py @@ -3,12 +3,14 @@ from typing import Mapping, Union import jsonschema - +import logging import pyhf.exceptions from pyhf import tensor from pyhf.schema import variables from pyhf.schema.loader import load_schema +log = logging.getLogger(__name__) + def _is_array_or_tensor(checker, instance): """ @@ -70,6 +72,10 @@ def validate( """ version = version or variables.SCHEMA_VERSION + if version != variables.SCHEMA_VERSION: + log.warning( + f"Specification requested version {version} but latest is {variables.SCHEMA_VERSION}. Upgrade your specification or downgrade pyhf." + ) schema = load_schema(str(Path(version).joinpath(schema_name))) From 9a67f9f4f75d85438f4494d1c928f439655c0267 Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Thu, 1 Sep 2022 11:50:09 -0700 Subject: [PATCH 06/39] add utility for upgrading specifications --- src/pyhf/schema/upgrader.py | 49 +++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 src/pyhf/schema/upgrader.py diff --git a/src/pyhf/schema/upgrader.py b/src/pyhf/schema/upgrader.py new file mode 100644 index 0000000000..a70623d30f --- /dev/null +++ b/src/pyhf/schema/upgrader.py @@ -0,0 +1,49 @@ +from pyhf.schema import variables +from typing import Mapping +import copy + + +def upgrade_workspace(spec: Mapping) -> Mapping: + """ + Upgrade the provided workspace specification to latest version. + + Args: + spec (dict): The specification to validate. + schema_name (str): The name of the schema to upgrade. + + Returns: + upgraded_spec (dict): Upgraded workspace specification. + + Raises: + pyhf.exceptions.InvalidSpecification: the specification is invalid + """ + + version = spec['version'] + + new_spec = copy.deepcopy(spec) + if version == '1.0.0': + new_spec['version'] = variables.SCHEMA_VERSION + return spec + + +def upgrade_patchset(spec: Mapping) -> Mapping: + """ + Upgrade the provided patchset specification to latest version. + + Args: + spec (dict): The specification to validate. + schema_name (str): The name of the schema to upgrade. + + Returns: + upgraded_spec (dict): Upgraded patchset specification. + + Raises: + pyhf.exceptions.InvalidSpecification: the specification is invalid + """ + + version = spec['version'] + + new_spec = copy.deepcopy(spec) + if version == '1.0.0': + new_spec['version'] = variables.SCHEMA_VERSION + return spec From c9da4e386e232128a0c301f0b8586b11c8d2890f Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Thu, 1 Sep 2022 13:30:55 -0700 Subject: [PATCH 07/39] add importlib_resources --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a5bea1a3c8..e9c80469aa 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -61,7 +61,7 @@ repos: name: mypy with Python 3.8 files: src additional_dependencies: - ['numpy', 'types-tqdm', 'click', 'types-jsonpatch', 'types-pyyaml', 'types-jsonschema', 'importlib_metadata', 'packaging'] + ['numpy', 'types-tqdm', 'click', 'types-jsonpatch', 'types-pyyaml', 'types-jsonschema', 'importlib_metadata', 'packaging', 'importlib_resources'] args: ["--python-version=3.8"] - <<: *mypy name: mypy with Python 3.11 From bc8de570cd73fc151d75fded26d59e3ae6ab0e99 Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Thu, 1 Sep 2022 13:31:10 -0700 Subject: [PATCH 08/39] add in new things into typing --- src/pyhf/typing.py | 51 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/src/pyhf/typing.py b/src/pyhf/typing.py index acaacce380..70accf150b 100644 --- a/src/pyhf/typing.py +++ b/src/pyhf/typing.py @@ -1,16 +1,24 @@ import os +import sys from typing import ( Any, Literal, + Mapping, MutableSequence, Protocol, Sequence, + SupportsFloat as Numeric, SupportsIndex, Tuple, TypedDict, Union, ) +if sys.version_info >= (3, 11): + from typing import NotRequired, Self +else: + from typing_extensions import NotRequired, Self + __all__ = ( "PathOrStr", "ParameterBase", @@ -27,11 +35,14 @@ "Modifier", "Sample", "Channel", + "Model", "Observation", + "PatchSet", "Workspace", "Literal", "TypedDict", "Protocol", + "Self", ) @@ -41,6 +52,9 @@ Shape = Tuple[int, ...] ShapeLike = Union[SupportsIndex, Sequence[SupportsIndex]] +Schema = Mapping[str, Any] +SchemaVersion = Literal['1.0.1', '1.0.0'] + class ParameterBase(TypedDict, total=False): auxdata: Sequence[float] @@ -119,14 +133,11 @@ class LumiSys(TypedDict): ] -class SampleBase(TypedDict, total=False): - parameter_configs: Sequence[Parameter] - - -class Sample(SampleBase): +class Sample(TypedDict): name: str data: Sequence[float] modifiers: Sequence[Modifier] + parameter_configs: NotRequired[Sequence[Parameter]] class Channel(TypedDict): @@ -139,11 +150,39 @@ class Observation(TypedDict): data: Sequence[float] +class Model(TypedDict): + channels: Sequence[Channel] + parameters: NotRequired[Sequence[Parameter]] + + class Workspace(TypedDict): measurements: Sequence[Measurement] channels: Sequence[Channel] observations: Sequence[Observation] - version: Literal['1.0.1', '1.0.0'] + version: SchemaVersion + + +class PatchMetadata(TypedDict): + name: str + values: Sequence[Union[Numeric, str]] + + +class Patch(TypedDict): + patch: Sequence[Mapping[str, Any]] + metadata: PatchMetadata + + +class PatchSetMetadata(TypedDict): + digests: Mapping[str, str] + labels: Sequence[str] + description: str + references: Mapping[str, str] + + +class PatchSet(TypedDict): + patches: Sequence[Patch] + metadata: PatchSetMetadata + version: SchemaVersion class TensorBackend(Protocol): From 475c08e1f7da75124a348cae32bb2105314c6223 Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Thu, 1 Sep 2022 13:32:07 -0700 Subject: [PATCH 09/39] add typing into schema --- src/pyhf/schema/__init__.py | 24 ++++++++++++++++-------- src/pyhf/schema/loader.py | 3 ++- src/pyhf/schema/upgrader.py | 6 +++--- src/pyhf/schema/validator.py | 11 +++++------ src/pyhf/schema/variables.py | 6 ++++-- 5 files changed, 30 insertions(+), 20 deletions(-) diff --git a/src/pyhf/schema/__init__.py b/src/pyhf/schema/__init__.py index 61bb01c78e..ea9955c98a 100644 --- a/src/pyhf/schema/__init__.py +++ b/src/pyhf/schema/__init__.py @@ -1,11 +1,18 @@ """ See :class:`~pyhf.schema.Schema` for documentation. """ -import pathlib +from __future__ import annotations import sys +from typing import Any from pyhf.schema.loader import load_schema from pyhf.schema.validator import validate from pyhf.schema import variables +from pyhf.typing import Self + +if sys.version_info >= (3, 9): + from importlib import resources +else: + import importlib_resources as resources __all__ = [ "load_schema", @@ -15,11 +22,11 @@ ] -def __dir__(): +def __dir__() -> list[str]: return __all__ -class Schema(sys.modules[__name__].__class__): +class Schema(sys.modules[__name__].__class__): # type: ignore[misc] """ A module-level wrapper around :mod:`pyhf.schema` which will provide additional functionality for interacting with schemas. @@ -61,7 +68,8 @@ class Schema(sys.modules[__name__].__class__): """ - def __call__(self, new_path: pathlib.Path): + # type ignore below, see https://github.com/python/mypy/pull/11666 + def __call__(self, new_path: resources.abc.Traversable) -> Self: # type: ignore[valid-type] """ Change the local search path for finding schemas locally. @@ -76,10 +84,10 @@ def __call__(self, new_path: pathlib.Path): variables.SCHEMA_CACHE.clear() return self - def __enter__(self): + def __enter__(self) -> None: pass - def __exit__(self, *args, **kwargs): + def __exit__(self, *args: Any, **kwargs: Any) -> None: """ Reset the local search path for finding schemas locally. @@ -90,14 +98,14 @@ def __exit__(self, *args, **kwargs): variables.SCHEMA_CACHE = self.orig_cache @property - def path(self): + def path(self) -> resources.abc.Traversable: """ The local path for schemas. """ return variables.schemas @property - def version(self): + def version(self) -> str: """ The default version used for finding schemas. """ diff --git a/src/pyhf/schema/loader.py b/src/pyhf/schema/loader.py index 7fb25f509e..154a8b740f 100644 --- a/src/pyhf/schema/loader.py +++ b/src/pyhf/schema/loader.py @@ -3,6 +3,7 @@ import json import pyhf.exceptions from pyhf.schema import variables +from pyhf.typing import Schema # 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 @@ -12,7 +13,7 @@ import importlib_resources as resources -def load_schema(schema_id: str): +def load_schema(schema_id: str) -> Schema: """ Get a schema by relative path from cache, or load it into the cache and return. diff --git a/src/pyhf/schema/upgrader.py b/src/pyhf/schema/upgrader.py index a70623d30f..c06827f92d 100644 --- a/src/pyhf/schema/upgrader.py +++ b/src/pyhf/schema/upgrader.py @@ -1,9 +1,9 @@ from pyhf.schema import variables -from typing import Mapping +from pyhf.typing import Workspace, PatchSet import copy -def upgrade_workspace(spec: Mapping) -> Mapping: +def upgrade_workspace(spec: Workspace) -> Workspace: """ Upgrade the provided workspace specification to latest version. @@ -26,7 +26,7 @@ def upgrade_workspace(spec: Mapping) -> Mapping: return spec -def upgrade_patchset(spec: Mapping) -> Mapping: +def upgrade_patchset(spec: PatchSet) -> PatchSet: """ Upgrade the provided patchset specification to latest version. diff --git a/src/pyhf/schema/validator.py b/src/pyhf/schema/validator.py index 3334d7b4e3..11f73eae66 100644 --- a/src/pyhf/schema/validator.py +++ b/src/pyhf/schema/validator.py @@ -1,13 +1,12 @@ import numbers from pathlib import Path -from typing import Mapping, Union - import jsonschema import logging import pyhf.exceptions from pyhf import tensor from pyhf.schema import variables from pyhf.schema.loader import load_schema +from pyhf.typing import Workspace, Model, Measurement, PatchSet log = logging.getLogger(__name__) @@ -37,12 +36,12 @@ def _is_number_or_tensor_subtype(checker, instance): def validate( - spec: Mapping, + spec: Workspace | Model | Measurement | PatchSet, schema_name: str, *, - version: Union[str, None] = None, + version: str | None = None, allow_tensors: bool = True, -): +) -> None: """ Validate the provided instance, ``spec``, against the schema associated with ``schema_name``. @@ -102,4 +101,4 @@ def validate( try: return validator.validate(spec) except jsonschema.ValidationError as err: - raise pyhf.exceptions.InvalidSpecification(err, schema_name) + 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 2f7c1d92ea..8258db6c7f 100644 --- a/src/pyhf/schema/variables.py +++ b/src/pyhf/schema/variables.py @@ -1,4 +1,6 @@ +from __future__ import annotations import sys +from pyhf.typing import Schema, SchemaVersion # 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 @@ -8,6 +10,6 @@ import importlib_resources as resources schemas = resources.files('pyhf') / "schemas" -SCHEMA_CACHE = {} +SCHEMA_CACHE: dict[str, Schema] = {} SCHEMA_BASE = "https://scikit-hep.org/pyhf/schemas/" -SCHEMA_VERSION = '1.0.1' +SCHEMA_VERSION: SchemaVersion = '1.0.1' From a4358c6f3d48fa60ad03335e18818495fe03f4f2 Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Thu, 1 Sep 2022 13:32:41 -0700 Subject: [PATCH 10/39] type schema --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e2a17df696..c057328d8a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -264,7 +264,6 @@ module = [ 'pyhf.modifiers.*', 'pyhf.exceptions.*', 'pyhf.parameters.*', - 'pyhf.schema.*', 'pyhf.writexml', 'pyhf.workspace', 'pyhf.patchset', From 8d67cc47a420408f94f4cea588e4421656729d73 Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Thu, 1 Sep 2022 15:07:41 -0700 Subject: [PATCH 11/39] fix it up --- src/pyhf/schema/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pyhf/schema/__init__.py b/src/pyhf/schema/__init__.py index ea9955c98a..3df081f854 100644 --- a/src/pyhf/schema/__init__.py +++ b/src/pyhf/schema/__init__.py @@ -10,9 +10,9 @@ from pyhf.typing import Self if sys.version_info >= (3, 9): - from importlib import resources + from importlib.abc import Traversable else: - import importlib_resources as resources + from importlib_resources.abc import Traversable __all__ = [ "load_schema", @@ -69,7 +69,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: resources.abc.Traversable) -> Self: # type: ignore[valid-type] + def __call__(self, new_path: Traversable) -> Self: # type: ignore[valid-type] """ Change the local search path for finding schemas locally. @@ -98,7 +98,7 @@ def __exit__(self, *args: Any, **kwargs: Any) -> None: variables.SCHEMA_CACHE = self.orig_cache @property - def path(self) -> resources.abc.Traversable: + def path(self) -> Traversable: """ The local path for schemas. """ From f30da279f15d68be4c5a878ac39df4d86da73605 Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Fri, 2 Sep 2022 11:23:19 -0700 Subject: [PATCH 12/39] fix typo --- src/pyhf/schema/upgrader.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pyhf/schema/upgrader.py b/src/pyhf/schema/upgrader.py index c06827f92d..ed16e90766 100644 --- a/src/pyhf/schema/upgrader.py +++ b/src/pyhf/schema/upgrader.py @@ -23,7 +23,7 @@ def upgrade_workspace(spec: Workspace) -> Workspace: new_spec = copy.deepcopy(spec) if version == '1.0.0': new_spec['version'] = variables.SCHEMA_VERSION - return spec + return new_spec def upgrade_patchset(spec: PatchSet) -> PatchSet: @@ -46,4 +46,4 @@ def upgrade_patchset(spec: PatchSet) -> PatchSet: new_spec = copy.deepcopy(spec) if version == '1.0.0': new_spec['version'] = variables.SCHEMA_VERSION - return spec + return new_spec From a2f4899733c72e6c68eb89ecfff66b989481ae9f Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Fri, 2 Sep 2022 11:23:45 -0700 Subject: [PATCH 13/39] add upgrade CLI --- src/pyhf/cli/cli.py | 4 ++- src/pyhf/cli/upgrade.py | 62 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 src/pyhf/cli/upgrade.py diff --git a/src/pyhf/cli/cli.py b/src/pyhf/cli/cli.py index a1a486fe54..c0113ff08e 100644 --- a/src/pyhf/cli/cli.py +++ b/src/pyhf/cli/cli.py @@ -4,7 +4,7 @@ import click from pyhf import __version__ -from pyhf.cli import rootio, spec, infer, patchset, complete +from pyhf.cli import rootio, spec, infer, patchset, complete, upgrade from pyhf.contrib import cli as contrib from pyhf import utils @@ -56,3 +56,5 @@ def pyhf(): pyhf.add_command(complete.cli) pyhf.add_command(contrib.cli) + +pyhf.add_command(upgrade.cli) diff --git a/src/pyhf/cli/upgrade.py b/src/pyhf/cli/upgrade.py new file mode 100644 index 0000000000..7399078208 --- /dev/null +++ b/src/pyhf/cli/upgrade.py @@ -0,0 +1,62 @@ +"""The pyhf spec CLI subcommand.""" +import logging + +import click +import json + +from pyhf.schema.upgrader import upgrade_workspace, upgrade_patchset + +log = logging.getLogger(__name__) + + +@click.group(name='upgrade') +def cli(): + """Operations for upgrading specifications.""" + + +@cli.command() +@click.argument('workspace', default='-') +@click.option( + '--output-file', + help='The location of the output json file. If not specified, prints to screen.', + default=None, +) +def workspace(workspace, output_file): + """ + Upgrade a HistFactory JSON workspace. + """ + with click.open_file(workspace, 'r') as specstream: + spec = json.load(specstream) + + ws = upgrade_workspace(spec) + + if output_file is None: + click.echo(json.dumps(ws, indent=4, sort_keys=True)) + else: + with open(output_file, 'w+') as out_file: + json.dump(ws, out_file, indent=4, sort_keys=True) + log.debug(f"Written to {output_file:s}") + + +@cli.command() +@click.argument('patchset', default='-') +@click.option( + '--output-file', + help='The location of the output json file. If not specified, prints to screen.', + default=None, +) +def patchset(patchset, output_file): + """ + Upgrade a pyhf JSON PatchSet. + """ + with click.open_file(patchset, 'r') as specstream: + spec = json.load(specstream) + + ps = upgrade_patchset(spec) + + if output_file is None: + click.echo(json.dumps(ps, indent=4, sort_keys=True)) + else: + with open(output_file, 'w+') as out_file: + json.dump(ps, out_file, indent=4, sort_keys=True) + log.debug(f"Written to {output_file:s}") From 41a0b23e80dfcacb7cf04f85e1889ee820d29d61 Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Fri, 2 Sep 2022 11:45:51 -0700 Subject: [PATCH 14/39] expose api --- src/pyhf/schema/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/pyhf/schema/__init__.py b/src/pyhf/schema/__init__.py index 3df081f854..5660d8e1c0 100644 --- a/src/pyhf/schema/__init__.py +++ b/src/pyhf/schema/__init__.py @@ -8,6 +8,7 @@ from pyhf.schema.validator import validate from pyhf.schema import variables from pyhf.typing import Self +from pyhf.schema.upgrader import upgrade_workspace, upgrade_patchset if sys.version_info >= (3, 9): from importlib.abc import Traversable @@ -19,6 +20,8 @@ "validate", "path", "version", + "upgrade_workspace", + "upgrade_patchset", ] From 58c79f0e17cd842ed13ab9148e8c084134b8aa3c Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Fri, 2 Sep 2022 11:54:03 -0700 Subject: [PATCH 15/39] add tests for the upgrade functionality in python --- tests/test_upgrade.py | 42 +++++++++++++ tests/test_upgrade/patchset_1.0.0.json | 29 +++++++++ tests/test_upgrade/workspace_1.0.0.json | 82 +++++++++++++++++++++++++ 3 files changed, 153 insertions(+) create mode 100644 tests/test_upgrade.py create mode 100644 tests/test_upgrade/patchset_1.0.0.json create mode 100644 tests/test_upgrade/workspace_1.0.0.json diff --git a/tests/test_upgrade.py b/tests/test_upgrade.py new file mode 100644 index 0000000000..3d2056d7c1 --- /dev/null +++ b/tests/test_upgrade.py @@ -0,0 +1,42 @@ +import pyhf +import pyhf.schema +import json +import logging + + +def test_1_0_0_workspace(datadir, caplog): + """ + Test upgrading a workspace from 1.0.0 + """ + spec = json.load(open(datadir.joinpath("workspace_1.0.0.json"))) + + with caplog.at_level(logging.INFO, 'pyhf.schema'): + pyhf.schema.validate(spec, 'workspace.json', version='1.0.0') + assert 'Specification requested version 1.0.0' in caplog.text + + caplog.clear() + + new_spec = pyhf.schema.upgrade_workspace(spec) + assert new_spec['version'] == '1.0.1' + with caplog.at_level(logging.INFO, 'pyhf.schema'): + pyhf.schema.validate(new_spec, 'workspace.json', version='1.0.1') + assert caplog.text == '' + + +def test_1_0_0_patchset(datadir, caplog): + """ + Test upgrading a patchset from 1.0.0 + """ + spec = json.load(open(datadir.joinpath("patchset_1.0.0.json"))) + + with caplog.at_level(logging.INFO, 'pyhf.schema'): + pyhf.schema.validate(spec, 'patchset.json', version='1.0.0') + assert 'Specification requested version 1.0.0' in caplog.text + + caplog.clear() + + new_spec = pyhf.schema.upgrade_patchset(spec) + assert new_spec['version'] == '1.0.1' + with caplog.at_level(logging.INFO, 'pyhf.schema'): + pyhf.schema.validate(new_spec, 'patchset.json', version='1.0.1') + assert caplog.text == '' diff --git a/tests/test_upgrade/patchset_1.0.0.json b/tests/test_upgrade/patchset_1.0.0.json new file mode 100644 index 0000000000..04dcee3b50 --- /dev/null +++ b/tests/test_upgrade/patchset_1.0.0.json @@ -0,0 +1,29 @@ +{ + "metadata": { + "references": { "hepdata": "ins1234567" }, + "description": "patchset for validation/xmlimport_input/config/example.xml", + "digests": { "sha256": "7c32ca3b8db75cbafcf5cd7ed4672fa2b1fa69e391c9b89068dd947a521866ec" }, + "labels": ["x"] + }, + "patches": [ + { + "metadata": { + "name": "patch_channel1_signal_syst1", + "values": [0] + }, + "patch": [ + { + "op": "replace", + "path": "/channels/0/samples/0/modifiers/0/data/hi", + "value": 1.2 + }, + { + "op": "replace", + "path": "/channels/0/samples/0/modifiers/0/data/lo", + "value": 0.8 + } + ] + } + ], + "version": "1.0.0" +} diff --git a/tests/test_upgrade/workspace_1.0.0.json b/tests/test_upgrade/workspace_1.0.0.json new file mode 100644 index 0000000000..bda01fa940 --- /dev/null +++ b/tests/test_upgrade/workspace_1.0.0.json @@ -0,0 +1,82 @@ +{ + "channels": [ + { + "name": "singlechannel", + "samples": [ + { + "data": [ + 5 + ], + "modifiers": [ + { + "data": null, + "name": "mu", + "type": "normfactor" + } + ], + "name": "signal" + }, + { + "data": [ + 50 + ], + "modifiers": [ + { + "data": [ + 6 + ], + "name": "uncorr_bkguncrt", + "type": "shapesys" + } + ], + "name": "background" + } + ] + } + ], + "measurements": [ + { + "config": { + "parameters": [ + { + "bounds": [ + [ + 0, + 10 + ] + ], + "fixed": false, + "inits": [ + 1 + ], + "name": "mu" + }, + { + "bounds": [ + [ + 1e-10, + 10 + ] + ], + "fixed": false, + "inits": [ + 1 + ], + "name": "uncorr_bkguncrt" + } + ], + "poi": "mu" + }, + "name": "measurement" + } + ], + "observations": [ + { + "data": [ + 50 + ], + "name": "singlechannel" + } + ], + "version": "1.0.0" +} From 7b60642049f856a714b196ef7a036bff8357b778 Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Tue, 6 Dec 2022 14:42:29 -0800 Subject: [PATCH 16/39] don't lock patchset and workspace together --- src/pyhf/readxml.py | 2 +- src/pyhf/schema/__init__.py | 9 ++------- src/pyhf/schema/upgrader.py | 4 ++-- src/pyhf/schema/validator.py | 24 +++++++++++++++--------- src/pyhf/schema/variables.py | 10 +++++++--- src/pyhf/typing.py | 8 +++++++- 6 files changed, 34 insertions(+), 23 deletions(-) diff --git a/src/pyhf/readxml.py b/src/pyhf/readxml.py index a694dab292..cb45d40839 100644 --- a/src/pyhf/readxml.py +++ b/src/pyhf/readxml.py @@ -473,7 +473,7 @@ def parse( 'measurements': measurements, 'channels': channels, 'observations': observations, - 'version': schema.version, # type: ignore[typeddict-unknown-key] + 'version': schema.versions['workspace.json'], # type: ignore[attr-defined] } try: schema.validate(result, 'workspace.json') diff --git a/src/pyhf/schema/__init__.py b/src/pyhf/schema/__init__.py index 5660d8e1c0..ce636349a8 100644 --- a/src/pyhf/schema/__init__.py +++ b/src/pyhf/schema/__init__.py @@ -7,14 +7,9 @@ from pyhf.schema.loader import load_schema from pyhf.schema.validator import validate from pyhf.schema import variables -from pyhf.typing import Self +from pyhf.typing import Self, SchemaVersion, Traversable from pyhf.schema.upgrader import upgrade_workspace, upgrade_patchset -if sys.version_info >= (3, 9): - from importlib.abc import Traversable -else: - from importlib_resources.abc import Traversable - __all__ = [ "load_schema", "validate", @@ -108,7 +103,7 @@ def path(self) -> Traversable: return variables.schemas @property - def version(self) -> str: + def versions(self) -> dict[str, SchemaVersion]: """ The default version used for finding schemas. """ diff --git a/src/pyhf/schema/upgrader.py b/src/pyhf/schema/upgrader.py index ed16e90766..1d747c6419 100644 --- a/src/pyhf/schema/upgrader.py +++ b/src/pyhf/schema/upgrader.py @@ -22,7 +22,7 @@ def upgrade_workspace(spec: Workspace) -> Workspace: new_spec = copy.deepcopy(spec) if version == '1.0.0': - new_spec['version'] = variables.SCHEMA_VERSION + new_spec['version'] = variables.SCHEMA_VERSION['workspace.json'] return new_spec @@ -45,5 +45,5 @@ def upgrade_patchset(spec: PatchSet) -> PatchSet: new_spec = copy.deepcopy(spec) if version == '1.0.0': - new_spec['version'] = variables.SCHEMA_VERSION + new_spec['version'] = variables.SCHEMA_VERSION['patchset.json'] return new_spec diff --git a/src/pyhf/schema/validator.py b/src/pyhf/schema/validator.py index 11f73eae66..40094a734f 100644 --- a/src/pyhf/schema/validator.py +++ b/src/pyhf/schema/validator.py @@ -7,11 +7,12 @@ from pyhf.schema import variables from pyhf.schema.loader import load_schema from pyhf.typing import Workspace, Model, Measurement, PatchSet +from typing import Any log = logging.getLogger(__name__) -def _is_array_or_tensor(checker, instance): +def _is_array_or_tensor(checker: jsonschema.TypeChecker, instance: Any) -> bool: """ A helper function for allowing the validation of tensors as list types in schema validation. @@ -19,10 +20,12 @@ def _is_array_or_tensor(checker, instance): This will check for valid array types using any backends that have been loaded so far. """ - return isinstance(instance, (list, *tensor.array_types)) + return isinstance(instance, (list, *tensor.array_types)) # type: ignore[attr-defined] -def _is_number_or_tensor_subtype(checker, instance): +def _is_number_or_tensor_subtype( + checker: jsonschema.TypeChecker, instance: Any +) -> bool: """ A helper function for allowing the validation of tensor contents as number types in schema validation. @@ -32,7 +35,7 @@ def _is_number_or_tensor_subtype(checker, instance): is_number = jsonschema._types.is_number(checker, instance) if is_number: return True - return isinstance(instance, (numbers.Number, *tensor.array_subtypes)) + return isinstance(instance, (numbers.Number, *tensor.array_subtypes)) # type: ignore[attr-defined] def validate( @@ -70,11 +73,14 @@ def validate( >>> """ - version = version or variables.SCHEMA_VERSION - if version != variables.SCHEMA_VERSION: - log.warning( - f"Specification requested version {version} but latest is {variables.SCHEMA_VERSION}. Upgrade your specification or downgrade pyhf." - ) + latest_known_version = variables.SCHEMA_VERSION.get(schema_name) + + if latest_known_version is not None: + version = version or latest_known_version + if version != latest_known_version: + log.warning( + f"Specification requested version {version} but latest is {latest_known_version}. Upgrade your specification or downgrade pyhf." + ) schema = load_schema(str(Path(version).joinpath(schema_name))) diff --git a/src/pyhf/schema/variables.py b/src/pyhf/schema/variables.py index 8258db6c7f..cbf9bfa3aa 100644 --- a/src/pyhf/schema/variables.py +++ b/src/pyhf/schema/variables.py @@ -1,6 +1,6 @@ from __future__ import annotations import sys -from pyhf.typing import Schema, SchemaVersion +from pyhf.typing import Schema, SchemaVersion, Traversable # 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 @@ -8,8 +8,12 @@ from importlib import resources else: import importlib_resources as resources -schemas = resources.files('pyhf') / "schemas" +schemas: Traversable = resources.files('pyhf') / "schemas" SCHEMA_CACHE: dict[str, Schema] = {} SCHEMA_BASE = "https://scikit-hep.org/pyhf/schemas/" -SCHEMA_VERSION: SchemaVersion = '1.0.1' +SCHEMA_VERSION: dict[str, SchemaVersion] = { + 'model.json': '1.0.1', + 'workspace.json': '1.0.1', + 'patchset.json': '1.0.0', +} diff --git a/src/pyhf/typing.py b/src/pyhf/typing.py index 70accf150b..5dea26b129 100644 --- a/src/pyhf/typing.py +++ b/src/pyhf/typing.py @@ -14,6 +14,11 @@ Union, ) +if sys.version_info >= (3, 9): + from importlib.abc import Traversable +else: + from importlib_resources.abc import Traversable + if sys.version_info >= (3, 11): from typing import NotRequired, Self else: @@ -40,9 +45,10 @@ "PatchSet", "Workspace", "Literal", - "TypedDict", "Protocol", "Self", + "Traversable", + "TypedDict", ) From 58fe1b7cef79e667ca5f3e59f3ad19f4aaa6942e Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Tue, 6 Dec 2022 15:00:47 -0800 Subject: [PATCH 17/39] fix some tests --- tests/test_public_api_repr.py | 2 ++ tests/test_schema.py | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test_public_api_repr.py b/tests/test_public_api_repr.py index f1482d8921..2c9a74301e 100644 --- a/tests/test_public_api_repr.py +++ b/tests/test_public_api_repr.py @@ -260,6 +260,8 @@ def test_schema_public_api(): assert dir(pyhf.schema) == [ "load_schema", "path", + "upgrade_patchset", + "upgrade_workspace", "validate", "version", ] diff --git a/tests/test_schema.py b/tests/test_schema.py index 384fcf0276..dec49c972e 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -22,9 +22,9 @@ def test_load_missing_schema(): def test_schema_attributes(): - assert hasattr(pyhf.schema, 'version') + assert hasattr(pyhf.schema, 'versions') assert hasattr(pyhf.schema, 'path') - assert pyhf.schema.version + assert pyhf.schema.versions assert pyhf.schema.path From 9b8ea833feac769d8270e3ae974d0f58f07f8257 Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Tue, 6 Dec 2022 15:25:49 -0800 Subject: [PATCH 18/39] fix broken tests --- src/pyhf/schema/variables.py | 2 +- tests/test_schema.py | 18 +++++++++++++----- .../customschema/{1.1.0 => 0.1.0}/defs.json | 6 +++--- .../{1.1.0 => 0.1.0}/jsonpatch.json | 2 +- .../{1.1.0 => 0.1.0}/measurement.json | 2 +- .../customschema/{1.1.0 => 0.1.0}/model.json | 2 +- .../{1.1.0 => 0.1.0}/patchset.json | 2 +- .../{1.1.0 => 0.1.0}/workspace.json | 2 +- tests/test_schema/customschema/custom.json | 2 +- 9 files changed, 23 insertions(+), 15 deletions(-) rename tests/test_schema/customschema/{1.1.0 => 0.1.0}/defs.json (98%) rename tests/test_schema/customschema/{1.1.0 => 0.1.0}/jsonpatch.json (60%) rename tests/test_schema/customschema/{1.1.0 => 0.1.0}/measurement.json (60%) rename tests/test_schema/customschema/{1.1.0 => 0.1.0}/model.json (61%) rename tests/test_schema/customschema/{1.1.0 => 0.1.0}/patchset.json (60%) rename tests/test_schema/customschema/{1.1.0 => 0.1.0}/workspace.json (60%) diff --git a/src/pyhf/schema/variables.py b/src/pyhf/schema/variables.py index cbf9bfa3aa..4b27d613b2 100644 --- a/src/pyhf/schema/variables.py +++ b/src/pyhf/schema/variables.py @@ -15,5 +15,5 @@ SCHEMA_VERSION: dict[str, SchemaVersion] = { 'model.json': '1.0.1', 'workspace.json': '1.0.1', - 'patchset.json': '1.0.0', + 'patchset.json': '1.0.1', } diff --git a/tests/test_schema.py b/tests/test_schema.py index dec49c972e..1715080e36 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -61,7 +61,11 @@ def test_schema_changeable(datadir, monkeypatch, self_restoring_schema_globals): assert len(pyhf.schema.variables.SCHEMA_CACHE) == 0 with open(new_path / "custom.json", encoding="utf-8") as spec_file: assert pyhf.Workspace(json.load(spec_file)) - assert len(pyhf.schema.variables.SCHEMA_CACHE) == 1 + assert len(pyhf.schema.variables.SCHEMA_CACHE) == 2 + assert list(pyhf.schema.variables.SCHEMA_CACHE) == [ + 'https://scikit-hep.org/pyhf/schemas/0.1.0/workspace.json', + 'https://scikit-hep.org/pyhf/schemas/0.1.0/defs.json', + ] def test_schema_changeable_context(datadir, monkeypatch, self_restoring_schema_globals): @@ -79,7 +83,11 @@ def test_schema_changeable_context(datadir, monkeypatch, self_restoring_schema_g assert len(pyhf.schema.variables.SCHEMA_CACHE) == 0 with open(new_path / "custom.json", encoding="utf-8") as spec_file: assert pyhf.Workspace(json.load(spec_file)) - assert len(pyhf.schema.variables.SCHEMA_CACHE) == 1 + assert len(pyhf.schema.variables.SCHEMA_CACHE) == 2 + assert list(pyhf.schema.variables.SCHEMA_CACHE) == [ + 'https://scikit-hep.org/pyhf/schemas/0.1.0/workspace.json', + 'https://scikit-hep.org/pyhf/schemas/0.1.0/defs.json', + ] assert old_path == pyhf.schema.path assert old_cache == pyhf.schema.variables.SCHEMA_CACHE @@ -544,7 +552,7 @@ def test_normsys_additional_properties(): ids=['add', 'replace', 'test', 'remove', 'move', 'copy'], ) def test_jsonpatch(patch): - pyhf.schema.validate([patch], 'jsonpatch.json') + pyhf.schema.validate([patch], 'jsonpatch.json', version='1.0.1') @pytest.mark.parametrize( @@ -570,14 +578,14 @@ def test_jsonpatch(patch): ) def test_jsonpatch_fail(patch): with pytest.raises(pyhf.exceptions.InvalidSpecification): - pyhf.schema.validate([patch], 'jsonpatch.json') + pyhf.schema.validate([patch], 'jsonpatch.json', version='1.0.1') @pytest.mark.parametrize('patchset_file', ['patchset_good.json']) def test_patchset(datadir, patchset_file): with open(datadir.joinpath(patchset_file), encoding="utf-8") as patch_file: patchset = json.load(patch_file) - pyhf.schema.validate(patchset, 'patchset.json') + pyhf.schema.validate(patchset, 'patchset.json', version='1.0.0') @pytest.mark.parametrize( diff --git a/tests/test_schema/customschema/1.1.0/defs.json b/tests/test_schema/customschema/0.1.0/defs.json similarity index 98% rename from tests/test_schema/customschema/1.1.0/defs.json rename to tests/test_schema/customschema/0.1.0/defs.json index dd63e8d32a..373c019fd9 100644 --- a/tests/test_schema/customschema/1.1.0/defs.json +++ b/tests/test_schema/customschema/0.1.0/defs.json @@ -1,6 +1,6 @@ { "$schema": "http://json-schema.org/draft-06/schema#", - "$id": "1.1.0/defs.json", + "$id": "https://scikit-hep.org/pyhf/schemas/0.1.0/defs.json", "definitions": { "workspace": { "type": "object", @@ -8,7 +8,7 @@ "channels": { "type": "array", "items": {"$ref": "#/definitions/channel"}, "minItems": 1 }, "measurements": { "type": "array", "items": {"$ref": "#/definitions/measurement"}, "minItems": 1 }, "observations": { "type": "array", "items": {"$ref": "#/definitions/observation" }, "minItems": 1 }, - "version": { "const": "1.1.0" } + "version": { "const": "0.1.0" } }, "additionalProperties": false, "required": ["channels", "measurements", "observations", "version"] @@ -247,7 +247,7 @@ "properties": { "patches": { "$ref": "#/definitions/patchset/patches" }, "metadata": { "$ref": "#/definitions/patchset/metadata" }, - "version": { "const": "1.1.0" } + "version": { "const": "0.1.0" } }, "additionalProperties": false, "required": ["patches", "metadata", "version"], diff --git a/tests/test_schema/customschema/1.1.0/jsonpatch.json b/tests/test_schema/customschema/0.1.0/jsonpatch.json similarity index 60% rename from tests/test_schema/customschema/1.1.0/jsonpatch.json rename to tests/test_schema/customschema/0.1.0/jsonpatch.json index 63b4496bc7..fb0958733e 100644 --- a/tests/test_schema/customschema/1.1.0/jsonpatch.json +++ b/tests/test_schema/customschema/0.1.0/jsonpatch.json @@ -1,5 +1,5 @@ { "$schema": "http://json-schema.org/draft-06/schema#", - "$id": "1.1.0/jsonpatch.json", + "$id": "https://scikit-hep.org/pyhf/schemas/0.1.0/jsonpatch.json", "$ref": "defs.json#/definitions/jsonpatch" } diff --git a/tests/test_schema/customschema/1.1.0/measurement.json b/tests/test_schema/customschema/0.1.0/measurement.json similarity index 60% rename from tests/test_schema/customschema/1.1.0/measurement.json rename to tests/test_schema/customschema/0.1.0/measurement.json index 124d84a522..3a5043bfe5 100644 --- a/tests/test_schema/customschema/1.1.0/measurement.json +++ b/tests/test_schema/customschema/0.1.0/measurement.json @@ -1,5 +1,5 @@ { "$schema": "http://json-schema.org/draft-06/schema#", - "$id": "1.1.0/measurement.json", + "$id": "https://scikit-hep.org/pyhf/schemas/0.1.0/measurement.json", "$ref": "defs.json#/definitions/measurement" } diff --git a/tests/test_schema/customschema/1.1.0/model.json b/tests/test_schema/customschema/0.1.0/model.json similarity index 61% rename from tests/test_schema/customschema/1.1.0/model.json rename to tests/test_schema/customschema/0.1.0/model.json index f44f47edc6..b7d1d3601b 100644 --- a/tests/test_schema/customschema/1.1.0/model.json +++ b/tests/test_schema/customschema/0.1.0/model.json @@ -1,5 +1,5 @@ { "$schema": "http://json-schema.org/draft-06/schema#", - "$id": "1.1.0/model.json", + "$id": "https://scikit-hep.org/pyhf/schemas/0.1.0/model.json", "$ref": "defs.json#/definitions/model" } diff --git a/tests/test_schema/customschema/1.1.0/patchset.json b/tests/test_schema/customschema/0.1.0/patchset.json similarity index 60% rename from tests/test_schema/customschema/1.1.0/patchset.json rename to tests/test_schema/customschema/0.1.0/patchset.json index c7f5596dc5..5ced7126e9 100644 --- a/tests/test_schema/customschema/1.1.0/patchset.json +++ b/tests/test_schema/customschema/0.1.0/patchset.json @@ -1,5 +1,5 @@ { "$schema": "http://json-schema.org/draft-06/schema#", - "$id": "1.1.0/patchset.json", + "$id": "https://scikit-hep.org/pyhf/schemas/0.1.0/patchset.json", "$ref": "defs.json#/definitions/patchset" } diff --git a/tests/test_schema/customschema/1.1.0/workspace.json b/tests/test_schema/customschema/0.1.0/workspace.json similarity index 60% rename from tests/test_schema/customschema/1.1.0/workspace.json rename to tests/test_schema/customschema/0.1.0/workspace.json index 5e91630381..8f439118c4 100644 --- a/tests/test_schema/customschema/1.1.0/workspace.json +++ b/tests/test_schema/customschema/0.1.0/workspace.json @@ -1,5 +1,5 @@ { "$schema": "http://json-schema.org/draft-06/schema#", - "$id": "1.1.0/workspace.json", + "$id": "https://scikit-hep.org/pyhf/schemas/0.1.0/workspace.json", "$ref": "defs.json#/definitions/workspace" } diff --git a/tests/test_schema/customschema/custom.json b/tests/test_schema/customschema/custom.json index f35ea110aa..028f5ed112 100644 --- a/tests/test_schema/customschema/custom.json +++ b/tests/test_schema/customschema/custom.json @@ -70,5 +70,5 @@ "name": "singlechannel" } ], - "version": "1.1.0" + "version": "0.1.0" } From 691a3b52e1d76099e72ec81106ff28a98103378e Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Tue, 6 Dec 2022 15:34:43 -0800 Subject: [PATCH 19/39] fix up more tests --- src/pyhf/workspace.py | 2 +- tests/test_workspace.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pyhf/workspace.py b/src/pyhf/workspace.py index 00abcf77f4..65e61e31de 100644 --- a/src/pyhf/workspace.py +++ b/src/pyhf/workspace.py @@ -816,7 +816,7 @@ def build(cls, model, data, name='measurement', validate: bool = True): """ workspace = copy.deepcopy(dict(channels=model.spec['channels'])) - workspace['version'] = schema.version + workspace['version'] = schema.versions['workspace.json'] workspace['measurements'] = [ { 'name': name, diff --git a/tests/test_workspace.py b/tests/test_workspace.py index 55ee0df046..0ceb53b5e8 100644 --- a/tests/test_workspace.py +++ b/tests/test_workspace.py @@ -452,6 +452,7 @@ def test_combine_workspace_incompatible_poi(workspace_factory, join): def test_combine_workspace_diff_version(workspace_factory, join): ws = workspace_factory() ws.version = '1.0.0' + ws['version'] = '1.0.0' new_ws = ws.rename( channels={channel: f'renamed_{channel}' for channel in ws.channels}, samples={sample: f'renamed_{sample}' for sample in ws.samples}, From 26a5f3277788eaee05a04b9a569b93616aa2895b Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Tue, 6 Dec 2022 15:38:58 -0800 Subject: [PATCH 20/39] fix pre-commit --- src/pyhf/cli/upgrade.py | 8 ++++---- tests/test_upgrade.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/pyhf/cli/upgrade.py b/src/pyhf/cli/upgrade.py index 7399078208..09a9175b67 100644 --- a/src/pyhf/cli/upgrade.py +++ b/src/pyhf/cli/upgrade.py @@ -25,7 +25,7 @@ def workspace(workspace, output_file): """ Upgrade a HistFactory JSON workspace. """ - with click.open_file(workspace, 'r') as specstream: + with click.open_file(workspace, 'r', encoding="utf-8") as specstream: spec = json.load(specstream) ws = upgrade_workspace(spec) @@ -33,7 +33,7 @@ def workspace(workspace, output_file): if output_file is None: click.echo(json.dumps(ws, indent=4, sort_keys=True)) else: - with open(output_file, 'w+') as out_file: + with open(output_file, 'w+', encoding="utf-8") as out_file: json.dump(ws, out_file, indent=4, sort_keys=True) log.debug(f"Written to {output_file:s}") @@ -49,7 +49,7 @@ def patchset(patchset, output_file): """ Upgrade a pyhf JSON PatchSet. """ - with click.open_file(patchset, 'r') as specstream: + with click.open_file(patchset, 'r', encoding="utf-8") as specstream: spec = json.load(specstream) ps = upgrade_patchset(spec) @@ -57,6 +57,6 @@ def patchset(patchset, output_file): if output_file is None: click.echo(json.dumps(ps, indent=4, sort_keys=True)) else: - with open(output_file, 'w+') as out_file: + with open(output_file, 'w+', encoding="utf-8") as out_file: json.dump(ps, out_file, indent=4, sort_keys=True) log.debug(f"Written to {output_file:s}") diff --git a/tests/test_upgrade.py b/tests/test_upgrade.py index 3d2056d7c1..981eac1567 100644 --- a/tests/test_upgrade.py +++ b/tests/test_upgrade.py @@ -8,7 +8,7 @@ def test_1_0_0_workspace(datadir, caplog): """ Test upgrading a workspace from 1.0.0 """ - spec = json.load(open(datadir.joinpath("workspace_1.0.0.json"))) + spec = json.load(open(datadir.joinpath("workspace_1.0.0.json"), encoding="utf-8")) with caplog.at_level(logging.INFO, 'pyhf.schema'): pyhf.schema.validate(spec, 'workspace.json', version='1.0.0') @@ -27,7 +27,7 @@ def test_1_0_0_patchset(datadir, caplog): """ Test upgrading a patchset from 1.0.0 """ - spec = json.load(open(datadir.joinpath("patchset_1.0.0.json"))) + spec = json.load(open(datadir.joinpath("patchset_1.0.0.json"), encoding="utf-8")) with caplog.at_level(logging.INFO, 'pyhf.schema'): pyhf.schema.validate(spec, 'patchset.json', version='1.0.0') From b75ab60551e2e31e57290a2609a018af04122172 Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Tue, 6 Dec 2022 15:44:17 -0800 Subject: [PATCH 21/39] add typing_extensions --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index c057328d8a..aea6ba02e0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,6 +46,7 @@ classifiers = [ dependencies = [ "click>=8.0.0", # for console scripts "importlib_resources>=1.4.0; python_version < '3.9'", # for resources in schema + "typing_extensions; python_version < '3.11'" # for typing "jsonpatch>=1.15", "jsonschema>=4.15.0", # for utils "pyyaml>=5.1", # for parsing CLI equal-delimited options From b15850cfb6739ecfd1056cdc30b0c595d15bb0b7 Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Tue, 6 Dec 2022 16:00:00 -0800 Subject: [PATCH 22/39] add 1.1.0 and keep latest schema version at 1.0.0 --- src/pyhf/schema/variables.py | 6 +++--- src/pyhf/schemas/{1.0.1 => 1.1.0}/defs.json | 0 src/pyhf/schemas/{1.0.1 => 1.1.0}/jsonpatch.json | 0 src/pyhf/schemas/{1.0.1 => 1.1.0}/measurement.json | 0 src/pyhf/schemas/{1.0.1 => 1.1.0}/model.json | 0 src/pyhf/schemas/{1.0.1 => 1.1.0}/patchset.json | 0 src/pyhf/schemas/{1.0.1 => 1.1.0}/workspace.json | 0 7 files changed, 3 insertions(+), 3 deletions(-) rename src/pyhf/schemas/{1.0.1 => 1.1.0}/defs.json (100%) rename src/pyhf/schemas/{1.0.1 => 1.1.0}/jsonpatch.json (100%) rename src/pyhf/schemas/{1.0.1 => 1.1.0}/measurement.json (100%) rename src/pyhf/schemas/{1.0.1 => 1.1.0}/model.json (100%) rename src/pyhf/schemas/{1.0.1 => 1.1.0}/patchset.json (100%) rename src/pyhf/schemas/{1.0.1 => 1.1.0}/workspace.json (100%) diff --git a/src/pyhf/schema/variables.py b/src/pyhf/schema/variables.py index 4b27d613b2..cb608e689a 100644 --- a/src/pyhf/schema/variables.py +++ b/src/pyhf/schema/variables.py @@ -13,7 +13,7 @@ SCHEMA_CACHE: dict[str, Schema] = {} SCHEMA_BASE = "https://scikit-hep.org/pyhf/schemas/" SCHEMA_VERSION: dict[str, SchemaVersion] = { - 'model.json': '1.0.1', - 'workspace.json': '1.0.1', - 'patchset.json': '1.0.1', + 'model.json': '1.0.0', + 'workspace.json': '1.0.0', + 'patchset.json': '1.0.0', } diff --git a/src/pyhf/schemas/1.0.1/defs.json b/src/pyhf/schemas/1.1.0/defs.json similarity index 100% rename from src/pyhf/schemas/1.0.1/defs.json rename to src/pyhf/schemas/1.1.0/defs.json diff --git a/src/pyhf/schemas/1.0.1/jsonpatch.json b/src/pyhf/schemas/1.1.0/jsonpatch.json similarity index 100% rename from src/pyhf/schemas/1.0.1/jsonpatch.json rename to src/pyhf/schemas/1.1.0/jsonpatch.json diff --git a/src/pyhf/schemas/1.0.1/measurement.json b/src/pyhf/schemas/1.1.0/measurement.json similarity index 100% rename from src/pyhf/schemas/1.0.1/measurement.json rename to src/pyhf/schemas/1.1.0/measurement.json diff --git a/src/pyhf/schemas/1.0.1/model.json b/src/pyhf/schemas/1.1.0/model.json similarity index 100% rename from src/pyhf/schemas/1.0.1/model.json rename to src/pyhf/schemas/1.1.0/model.json diff --git a/src/pyhf/schemas/1.0.1/patchset.json b/src/pyhf/schemas/1.1.0/patchset.json similarity index 100% rename from src/pyhf/schemas/1.0.1/patchset.json rename to src/pyhf/schemas/1.1.0/patchset.json diff --git a/src/pyhf/schemas/1.0.1/workspace.json b/src/pyhf/schemas/1.1.0/workspace.json similarity index 100% rename from src/pyhf/schemas/1.0.1/workspace.json rename to src/pyhf/schemas/1.1.0/workspace.json From b91ee7622296b51f5c08ddb63bc247a4552d0772 Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Tue, 6 Dec 2022 16:00:55 -0800 Subject: [PATCH 23/39] fix vers --- src/pyhf/patchset.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pyhf/patchset.py b/src/pyhf/patchset.py index 605aa8ce34..b72118835b 100644 --- a/src/pyhf/patchset.py +++ b/src/pyhf/patchset.py @@ -117,11 +117,11 @@ class PatchSet: ... ] ... } ... ], - ... "version": "1.0.1" + ... "version": "1.0.0" ... }) ... >>> patchset.version - '1.0.1' + '1.0.0' >>> patchset.references {'hepdata': 'ins1234567'} >>> patchset.description From c89eff0b2c2bbe4263032bce3cd72f677d81d815 Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Wed, 7 Dec 2022 07:37:54 -0800 Subject: [PATCH 24/39] fix up test --- tests/test_schema.py | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/tests/test_schema.py b/tests/test_schema.py index 1715080e36..d41cfc16e1 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -8,12 +8,25 @@ import pyhf -@pytest.mark.parametrize('version', ['1.0.0']) @pytest.mark.parametrize( - 'schema', ['defs.json', 'measurement.json', 'model.json', 'workspace.json'] + 'schema', + [ + "1.0.0/model.json", + "1.0.0/workspace.json", + "1.0.0/defs.json", + "1.0.0/jsonpatch.json", + "1.0.0/measurement.json", + "1.0.0/patchset.json", + "1.1.0/model.json", + "1.1.0/workspace.json", + "1.1.0/defs.json", + "1.1.0/jsonpatch.json", + "1.1.0/measurement.json", + "1.1.0/patchset.json", + ], ) -def test_get_schema(version, schema): - assert pyhf.schema.load_schema(f'{version}/{schema}') +def test_get_schema(schema): + assert pyhf.schema.load_schema(schema) def test_load_missing_schema(): @@ -551,8 +564,9 @@ def test_normsys_additional_properties(): ], ids=['add', 'replace', 'test', 'remove', 'move', 'copy'], ) -def test_jsonpatch(patch): - pyhf.schema.validate([patch], 'jsonpatch.json', version='1.0.1') +@pytest.mark.parametrize('version', ['1.0.0']) +def test_jsonpatch(patch, version): + pyhf.schema.validate([patch], 'jsonpatch.json', version=version) @pytest.mark.parametrize( @@ -576,16 +590,18 @@ def test_jsonpatch(patch): 'move_nopath', ], ) -def test_jsonpatch_fail(patch): +@pytest.mark.parametrize('version', ['1.0.0']) +def test_jsonpatch_fail(patch, version): with pytest.raises(pyhf.exceptions.InvalidSpecification): - pyhf.schema.validate([patch], 'jsonpatch.json', version='1.0.1') + pyhf.schema.validate([patch], 'jsonpatch.json', version=version) @pytest.mark.parametrize('patchset_file', ['patchset_good.json']) -def test_patchset(datadir, patchset_file): +@pytest.mark.parametrize('version', ['1.0.0']) +def test_patchset(datadir, patchset_file, version): with open(datadir.joinpath(patchset_file), encoding="utf-8") as patch_file: patchset = json.load(patch_file) - pyhf.schema.validate(patchset, 'patchset.json', version='1.0.0') + pyhf.schema.validate(patchset, 'patchset.json', version=version) @pytest.mark.parametrize( From 8f10cd8b7e2cad1967d4b086f544359ae726dc72 Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Wed, 7 Dec 2022 07:38:07 -0800 Subject: [PATCH 25/39] fix typing --- src/pyhf/schema/upgrader.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/pyhf/schema/upgrader.py b/src/pyhf/schema/upgrader.py index 1d747c6419..851ba9d923 100644 --- a/src/pyhf/schema/upgrader.py +++ b/src/pyhf/schema/upgrader.py @@ -1,9 +1,9 @@ from pyhf.schema import variables -from pyhf.typing import Workspace, PatchSet +from pyhf.typing import Workspace, PatchSet, SchemaVersion import copy -def upgrade_workspace(spec: Workspace) -> Workspace: +def upgrade_workspace(spec: Workspace, *, to_version: SchemaVersion) -> Workspace: """ Upgrade the provided workspace specification to latest version. @@ -19,6 +19,10 @@ def upgrade_workspace(spec: Workspace) -> Workspace: """ version = spec['version'] + latest_version = variables.SCHEMA_VERSION['workspace.json'] + + if version == latest_version: + return spec new_spec = copy.deepcopy(spec) if version == '1.0.0': From b6edb609edb7da1aa87bcc3ba93950896686f0d5 Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Wed, 7 Dec 2022 08:52:03 -0800 Subject: [PATCH 26/39] switch to class-based so we can always upgrade between diff versions --- src/pyhf/cli/upgrade.py | 20 ++++++-- src/pyhf/schema/__init__.py | 5 +- src/pyhf/schema/upgrader.py | 87 ++++++++++++++++++++--------------- src/pyhf/typing.py | 12 +++++ tests/test_public_api_repr.py | 3 +- tests/test_upgrade.py | 4 +- 6 files changed, 83 insertions(+), 48 deletions(-) diff --git a/src/pyhf/cli/upgrade.py b/src/pyhf/cli/upgrade.py index 09a9175b67..55830b7a0d 100644 --- a/src/pyhf/cli/upgrade.py +++ b/src/pyhf/cli/upgrade.py @@ -4,7 +4,7 @@ import click import json -from pyhf.schema.upgrader import upgrade_workspace, upgrade_patchset +from pyhf.schema.upgrader import upgrade log = logging.getLogger(__name__) @@ -16,19 +16,24 @@ def cli(): @cli.command() @click.argument('workspace', default='-') +@click.option( + '--version', + help='The version to upgrade to', + default=None, +) @click.option( '--output-file', help='The location of the output json file. If not specified, prints to screen.', default=None, ) -def workspace(workspace, output_file): +def workspace(workspace, version, output_file): """ Upgrade a HistFactory JSON workspace. """ with click.open_file(workspace, 'r', encoding="utf-8") as specstream: spec = json.load(specstream) - ws = upgrade_workspace(spec) + ws = upgrade(to_version=version).workspace(spec) if output_file is None: click.echo(json.dumps(ws, indent=4, sort_keys=True)) @@ -40,19 +45,24 @@ def workspace(workspace, output_file): @cli.command() @click.argument('patchset', default='-') +@click.option( + '--version', + help='The version to upgrade to', + default=None, +) @click.option( '--output-file', help='The location of the output json file. If not specified, prints to screen.', default=None, ) -def patchset(patchset, output_file): +def patchset(patchset, version, output_file): """ Upgrade a pyhf JSON PatchSet. """ with click.open_file(patchset, 'r', encoding="utf-8") as specstream: spec = json.load(specstream) - ps = upgrade_patchset(spec) + ps = upgrade(to_version=version).patchset(spec) if output_file is None: click.echo(json.dumps(ps, indent=4, sort_keys=True)) diff --git a/src/pyhf/schema/__init__.py b/src/pyhf/schema/__init__.py index ce636349a8..e527769c87 100644 --- a/src/pyhf/schema/__init__.py +++ b/src/pyhf/schema/__init__.py @@ -8,15 +8,14 @@ from pyhf.schema.validator import validate from pyhf.schema import variables from pyhf.typing import Self, SchemaVersion, Traversable -from pyhf.schema.upgrader import upgrade_workspace, upgrade_patchset +from pyhf.schema.upgrader import upgrade __all__ = [ "load_schema", "validate", "path", "version", - "upgrade_workspace", - "upgrade_patchset", + "upgrade", ] diff --git a/src/pyhf/schema/upgrader.py b/src/pyhf/schema/upgrader.py index 851ba9d923..91fd81eced 100644 --- a/src/pyhf/schema/upgrader.py +++ b/src/pyhf/schema/upgrader.py @@ -1,53 +1,68 @@ +from __future__ import annotations + from pyhf.schema import variables -from pyhf.typing import Workspace, PatchSet, SchemaVersion +from pyhf.typing import Workspace, PatchSet, SchemaVersion, UpgradeProtocol import copy -def upgrade_workspace(spec: Workspace, *, to_version: SchemaVersion) -> Workspace: - """ - Upgrade the provided workspace specification to latest version. +class Upgrade_1_0_1: + version: SchemaVersion = '1.0.1' + + @classmethod + def workspace(cls, spec: Workspace) -> Workspace: + """ + Upgrade the provided workspace specification. + + Args: + spec (dict): The specification to validate. + schema_name (str): The name of the schema to upgrade. + + Returns: + upgraded_spec (dict): Upgraded workspace specification. + + Raises: + pyhf.exceptions.InvalidSpecification: the specification is invalid + """ - Args: - spec (dict): The specification to validate. - schema_name (str): The name of the schema to upgrade. + version = spec['version'] + latest_version = variables.SCHEMA_VERSION['workspace.json'] - Returns: - upgraded_spec (dict): Upgraded workspace specification. + if version == latest_version: + return spec - Raises: - pyhf.exceptions.InvalidSpecification: the specification is invalid - """ + new_spec = copy.deepcopy(spec) + if version == '1.0.0': + new_spec['version'] = cls.version + return new_spec - version = spec['version'] - latest_version = variables.SCHEMA_VERSION['workspace.json'] + @classmethod + def patchset(cls, spec: PatchSet) -> PatchSet: + """ + Upgrade the provided patchset specification. - if version == latest_version: - return spec + Args: + spec (dict): The specification to validate. + schema_name (str): The name of the schema to upgrade. - new_spec = copy.deepcopy(spec) - if version == '1.0.0': - new_spec['version'] = variables.SCHEMA_VERSION['workspace.json'] - return new_spec + Returns: + upgraded_spec (dict): Upgraded patchset specification. + Raises: + pyhf.exceptions.InvalidSpecification: the specification is invalid + """ -def upgrade_patchset(spec: PatchSet) -> PatchSet: - """ - Upgrade the provided patchset specification to latest version. + version = spec['version'] - Args: - spec (dict): The specification to validate. - schema_name (str): The name of the schema to upgrade. + new_spec = copy.deepcopy(spec) + if version == '1.0.0': + new_spec['version'] = cls.version + return new_spec - Returns: - upgraded_spec (dict): Upgraded patchset specification. - Raises: - pyhf.exceptions.InvalidSpecification: the specification is invalid - """ +def upgrade(*, to_version: SchemaVersion | None = None) -> type[UpgradeProtocol]: + to_version = to_version or variables.SCHEMA_VERSION['workspace.json'] - version = spec['version'] + if to_version == '1.0.1': + return Upgrade_1_0_1 - new_spec = copy.deepcopy(spec) - if version == '1.0.0': - new_spec['version'] = variables.SCHEMA_VERSION['patchset.json'] - return new_spec + raise ValueError(f'{to_version} is not a valid version to upgrade to.') diff --git a/src/pyhf/typing.py b/src/pyhf/typing.py index 5dea26b129..0bea2a65ce 100644 --- a/src/pyhf/typing.py +++ b/src/pyhf/typing.py @@ -210,3 +210,15 @@ def sample(self, sample_shape: Shape) -> Any: def log_prob(self, value: Any) -> Any: ... + + +class UpgradeProtocol(Protocol): + version: SchemaVersion + + @classmethod + def workspace(cls, spec: Workspace) -> Workspace: + ... + + @classmethod + def patchset(cls, spec: PatchSet) -> PatchSet: + ... diff --git a/tests/test_public_api_repr.py b/tests/test_public_api_repr.py index 2c9a74301e..7bd4141276 100644 --- a/tests/test_public_api_repr.py +++ b/tests/test_public_api_repr.py @@ -260,8 +260,7 @@ def test_schema_public_api(): assert dir(pyhf.schema) == [ "load_schema", "path", - "upgrade_patchset", - "upgrade_workspace", + "upgrade", "validate", "version", ] diff --git a/tests/test_upgrade.py b/tests/test_upgrade.py index 981eac1567..4b115295ae 100644 --- a/tests/test_upgrade.py +++ b/tests/test_upgrade.py @@ -16,7 +16,7 @@ def test_1_0_0_workspace(datadir, caplog): caplog.clear() - new_spec = pyhf.schema.upgrade_workspace(spec) + new_spec = pyhf.schema.upgrade('1.0.1').workspace(spec) assert new_spec['version'] == '1.0.1' with caplog.at_level(logging.INFO, 'pyhf.schema'): pyhf.schema.validate(new_spec, 'workspace.json', version='1.0.1') @@ -35,7 +35,7 @@ def test_1_0_0_patchset(datadir, caplog): caplog.clear() - new_spec = pyhf.schema.upgrade_patchset(spec) + new_spec = pyhf.schema.upgrade('1.0.1').patchset(spec) assert new_spec['version'] == '1.0.1' with caplog.at_level(logging.INFO, 'pyhf.schema'): pyhf.schema.validate(new_spec, 'patchset.json', version='1.0.1') From f854c2393652bd318555af51046a854e4519e408 Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Wed, 7 Dec 2022 09:04:34 -0800 Subject: [PATCH 27/39] fix up tests for now --- src/pyhf/schema/upgrader.py | 4 ++++ tests/test_upgrade.py | 16 ++++++---------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/pyhf/schema/upgrader.py b/src/pyhf/schema/upgrader.py index 91fd81eced..afe93ab5a6 100644 --- a/src/pyhf/schema/upgrader.py +++ b/src/pyhf/schema/upgrader.py @@ -6,6 +6,10 @@ class Upgrade_1_0_1: + """ + Used for testing functionality of upgrade. + """ + version: SchemaVersion = '1.0.1' @classmethod diff --git a/tests/test_upgrade.py b/tests/test_upgrade.py index 4b115295ae..9e16c17866 100644 --- a/tests/test_upgrade.py +++ b/tests/test_upgrade.py @@ -4,39 +4,35 @@ import logging -def test_1_0_0_workspace(datadir, caplog): +def test_1_0_0_workspace(datadir, caplog, monkeypatch): """ Test upgrading a workspace from 1.0.0 """ spec = json.load(open(datadir.joinpath("workspace_1.0.0.json"), encoding="utf-8")) + monkeypatch.setitem(pyhf.schema.versions, 'workspace.json', '1.0.1') with caplog.at_level(logging.INFO, 'pyhf.schema'): pyhf.schema.validate(spec, 'workspace.json', version='1.0.0') assert 'Specification requested version 1.0.0' in caplog.text caplog.clear() - new_spec = pyhf.schema.upgrade('1.0.1').workspace(spec) + new_spec = pyhf.schema.upgrade(to_version='1.0.1').workspace(spec) assert new_spec['version'] == '1.0.1' - with caplog.at_level(logging.INFO, 'pyhf.schema'): - pyhf.schema.validate(new_spec, 'workspace.json', version='1.0.1') - assert caplog.text == '' -def test_1_0_0_patchset(datadir, caplog): +def test_1_0_0_patchset(datadir, caplog, monkeypatch): """ Test upgrading a patchset from 1.0.0 """ spec = json.load(open(datadir.joinpath("patchset_1.0.0.json"), encoding="utf-8")) + monkeypatch.setitem(pyhf.schema.versions, 'patchset.json', '1.0.1') with caplog.at_level(logging.INFO, 'pyhf.schema'): pyhf.schema.validate(spec, 'patchset.json', version='1.0.0') assert 'Specification requested version 1.0.0' in caplog.text caplog.clear() - new_spec = pyhf.schema.upgrade('1.0.1').patchset(spec) + new_spec = pyhf.schema.upgrade(to_version='1.0.1').patchset(spec) assert new_spec['version'] == '1.0.1' - with caplog.at_level(logging.INFO, 'pyhf.schema'): - pyhf.schema.validate(new_spec, 'patchset.json', version='1.0.1') - assert caplog.text == '' From 4f84a655fdacb289a53c934525c5d35759c769ca Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Wed, 7 Dec 2022 09:36:28 -0800 Subject: [PATCH 28/39] fix up coverage --- src/pyhf/cli/upgrade.py | 2 +- src/pyhf/schema/upgrader.py | 17 +++--------- tests/test_scripts.py | 36 +++++++++++++++++++++++++ tests/test_scripts/patchset_1.0.0.json | 1 + tests/test_scripts/workspace_1.0.0.json | 1 + tests/test_upgrade.py | 14 ++++++++++ 6 files changed, 56 insertions(+), 15 deletions(-) create mode 120000 tests/test_scripts/patchset_1.0.0.json create mode 120000 tests/test_scripts/workspace_1.0.0.json diff --git a/src/pyhf/cli/upgrade.py b/src/pyhf/cli/upgrade.py index 55830b7a0d..6fc8ba163c 100644 --- a/src/pyhf/cli/upgrade.py +++ b/src/pyhf/cli/upgrade.py @@ -1,4 +1,4 @@ -"""The pyhf spec CLI subcommand.""" +"""The pyhf upgrade CLI subcommand.""" import logging import click diff --git a/src/pyhf/schema/upgrader.py b/src/pyhf/schema/upgrader.py index afe93ab5a6..4b7c459cc9 100644 --- a/src/pyhf/schema/upgrader.py +++ b/src/pyhf/schema/upgrader.py @@ -1,6 +1,5 @@ from __future__ import annotations -from pyhf.schema import variables from pyhf.typing import Workspace, PatchSet, SchemaVersion, UpgradeProtocol import copy @@ -28,14 +27,8 @@ def workspace(cls, spec: Workspace) -> Workspace: pyhf.exceptions.InvalidSpecification: the specification is invalid """ - version = spec['version'] - latest_version = variables.SCHEMA_VERSION['workspace.json'] - - if version == latest_version: - return spec - new_spec = copy.deepcopy(spec) - if version == '1.0.0': + if spec['version'] == '1.0.0': new_spec['version'] = cls.version return new_spec @@ -55,18 +48,14 @@ def patchset(cls, spec: PatchSet) -> PatchSet: pyhf.exceptions.InvalidSpecification: the specification is invalid """ - version = spec['version'] - new_spec = copy.deepcopy(spec) - if version == '1.0.0': + if spec['version'] == '1.0.0': new_spec['version'] = cls.version return new_spec def upgrade(*, to_version: SchemaVersion | None = None) -> type[UpgradeProtocol]: - to_version = to_version or variables.SCHEMA_VERSION['workspace.json'] - - if to_version == '1.0.1': + if to_version is None or to_version == '1.0.1': return Upgrade_1_0_1 raise ValueError(f'{to_version} is not a valid version to upgrade to.') diff --git a/tests/test_scripts.py b/tests/test_scripts.py index 9d00814c8e..c7dd694295 100644 --- a/tests/test_scripts.py +++ b/tests/test_scripts.py @@ -770,3 +770,39 @@ def test_sort_outfile(tmp_path, script_runner): ret = script_runner.run(shlex.split(command)) assert ret.success + + +@pytest.mark.parametrize('output_file', [False, True]) +def test_upgrade_workspace(tmpdir, datadir, script_runner, output_file): + temp = tmpdir.join("upgraded_output.json") + command = f'pyhf upgrade workspace {datadir.joinpath("workspace_1.0.0.json")} --version 1.0.1' + if output_file: + command += f" --output-file {temp.strpath}" + + ret = script_runner.run(*shlex.split(command)) + + assert ret.success + if output_file: + extracted_output = json.loads(temp.read()) + else: + extracted_output = json.loads(ret.stdout) + + assert extracted_output['version'] == '1.0.1' + + +@pytest.mark.parametrize('output_file', [False, True]) +def test_upgrade_patchset(tmpdir, datadir, script_runner, output_file): + temp = tmpdir.join("upgraded_output.json") + command = f'pyhf upgrade patchset {datadir.joinpath("patchset_1.0.0.json")} --version 1.0.1' + if output_file: + command += f" --output-file {temp.strpath}" + + ret = script_runner.run(*shlex.split(command)) + + assert ret.success + if output_file: + extracted_output = json.loads(temp.read()) + else: + extracted_output = json.loads(ret.stdout) + + assert extracted_output['version'] == '1.0.1' diff --git a/tests/test_scripts/patchset_1.0.0.json b/tests/test_scripts/patchset_1.0.0.json new file mode 120000 index 0000000000..962dd90d3a --- /dev/null +++ b/tests/test_scripts/patchset_1.0.0.json @@ -0,0 +1 @@ +../test_upgrade/patchset_1.0.0.json \ No newline at end of file diff --git a/tests/test_scripts/workspace_1.0.0.json b/tests/test_scripts/workspace_1.0.0.json new file mode 120000 index 0000000000..59100bc292 --- /dev/null +++ b/tests/test_scripts/workspace_1.0.0.json @@ -0,0 +1 @@ +../test_upgrade/workspace_1.0.0.json \ No newline at end of file diff --git a/tests/test_upgrade.py b/tests/test_upgrade.py index 9e16c17866..3c9524c4a6 100644 --- a/tests/test_upgrade.py +++ b/tests/test_upgrade.py @@ -2,6 +2,20 @@ import pyhf.schema import json import logging +import pytest + + +def test_upgrade_bad_version(datadir): + with pytest.raises(ValueError): + pyhf.schema.upgrade(to_version='0.9.0') + + +def test_upgrade_to_latest(datadir): + ws = json.load(open(datadir.joinpath("workspace_1.0.0.json"), encoding="utf-8")) + pyhf.schema.upgrade().workspace(ws) + + ps = json.load(open(datadir.joinpath("workspace_1.0.0.json"), encoding="utf-8")) + pyhf.schema.upgrade().patchset(ps) def test_1_0_0_workspace(datadir, caplog, monkeypatch): From 06f13a9a5b5d09f46e944063cee580e962685572 Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Wed, 7 Dec 2022 09:44:09 -0800 Subject: [PATCH 29/39] update version to 1.1.0 --- src/pyhf/schemas/1.1.0/defs.json | 6 +++--- src/pyhf/schemas/1.1.0/jsonpatch.json | 2 +- src/pyhf/schemas/1.1.0/measurement.json | 2 +- src/pyhf/schemas/1.1.0/model.json | 2 +- src/pyhf/schemas/1.1.0/patchset.json | 2 +- src/pyhf/schemas/1.1.0/workspace.json | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/pyhf/schemas/1.1.0/defs.json b/src/pyhf/schemas/1.1.0/defs.json index 347975088c..02e316eb5c 100644 --- a/src/pyhf/schemas/1.1.0/defs.json +++ b/src/pyhf/schemas/1.1.0/defs.json @@ -1,6 +1,6 @@ { "$schema": "http://json-schema.org/draft/2020-12/schema#", - "$id": "https://scikit-hep.org/pyhf/schemas/1.0.1/defs.json", + "$id": "https://scikit-hep.org/pyhf/schemas/1.1.0/defs.json", "definitions": { "workspace": { "type": "object", @@ -8,7 +8,7 @@ "channels": { "type": "array", "items": {"$ref": "#/definitions/channel"}, "minItems": 1 }, "measurements": { "type": "array", "items": {"$ref": "#/definitions/measurement"}, "minItems": 1 }, "observations": { "type": "array", "items": {"$ref": "#/definitions/observation" }, "minItems": 1 }, - "version": { "const": "1.0.1" } + "version": { "const": "1.1.0" } }, "additionalProperties": false, "required": ["channels", "measurements", "observations", "version"] @@ -247,7 +247,7 @@ "properties": { "patches": { "$ref": "#/definitions/patchset/patches" }, "metadata": { "$ref": "#/definitions/patchset/metadata" }, - "version": { "const": "1.0.1" } + "version": { "const": "1.1.0" } }, "additionalProperties": false, "required": ["patches", "metadata", "version"], diff --git a/src/pyhf/schemas/1.1.0/jsonpatch.json b/src/pyhf/schemas/1.1.0/jsonpatch.json index c5886eb6ba..93b7aba6b0 100644 --- a/src/pyhf/schemas/1.1.0/jsonpatch.json +++ b/src/pyhf/schemas/1.1.0/jsonpatch.json @@ -1,5 +1,5 @@ { "$schema": "http://json-schema.org/draft/2020-12/schema#", - "$id": "https://scikit-hep.org/pyhf/schemas/1.0.1/jsonpatch.json", + "$id": "https://scikit-hep.org/pyhf/schemas/1.1.0/jsonpatch.json", "$ref": "defs.json#/definitions/jsonpatch" } diff --git a/src/pyhf/schemas/1.1.0/measurement.json b/src/pyhf/schemas/1.1.0/measurement.json index d7da3dddd6..09db479667 100644 --- a/src/pyhf/schemas/1.1.0/measurement.json +++ b/src/pyhf/schemas/1.1.0/measurement.json @@ -1,5 +1,5 @@ { "$schema": "http://json-schema.org/draft/2020-12/schema#", - "$id": "https://scikit-hep.org/pyhf/schemas/1.0.1/measurement.json", + "$id": "https://scikit-hep.org/pyhf/schemas/1.1.0/measurement.json", "$ref": "defs.json#/definitions/measurement" } diff --git a/src/pyhf/schemas/1.1.0/model.json b/src/pyhf/schemas/1.1.0/model.json index 4b9a8f15d8..a3d6e6ae4a 100644 --- a/src/pyhf/schemas/1.1.0/model.json +++ b/src/pyhf/schemas/1.1.0/model.json @@ -1,5 +1,5 @@ { "$schema": "http://json-schema.org/draft/2020-12/schema#", - "$id": "https://scikit-hep.org/pyhf/schemas/1.0.1/model.json", + "$id": "https://scikit-hep.org/pyhf/schemas/1.1.0/model.json", "$ref": "defs.json#/definitions/model" } diff --git a/src/pyhf/schemas/1.1.0/patchset.json b/src/pyhf/schemas/1.1.0/patchset.json index 0f0bacc5ca..f453981dad 100644 --- a/src/pyhf/schemas/1.1.0/patchset.json +++ b/src/pyhf/schemas/1.1.0/patchset.json @@ -1,5 +1,5 @@ { "$schema": "http://json-schema.org/draft/2020-12/schema#", - "$id": "https://scikit-hep.org/pyhf/schemas/1.0.1/patchset.json", + "$id": "https://scikit-hep.org/pyhf/schemas/1.1.0/patchset.json", "$ref": "defs.json#/definitions/patchset" } diff --git a/src/pyhf/schemas/1.1.0/workspace.json b/src/pyhf/schemas/1.1.0/workspace.json index 200d3ba58e..22ee3e3736 100644 --- a/src/pyhf/schemas/1.1.0/workspace.json +++ b/src/pyhf/schemas/1.1.0/workspace.json @@ -1,5 +1,5 @@ { "$schema": "http://json-schema.org/draft/2020-12/schema#", - "$id": "https://scikit-hep.org/pyhf/schemas/1.0.1/workspace.json", + "$id": "https://scikit-hep.org/pyhf/schemas/1.1.0/workspace.json", "$ref": "defs.json#/definitions/workspace" } From 4563ae3ba17ebda9a6257e1142736c054f031c35 Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Wed, 7 Dec 2022 09:49:02 -0800 Subject: [PATCH 30/39] update draft --- src/pyhf/schemas/1.0.0/defs.json | 2 +- src/pyhf/schemas/1.0.0/jsonpatch.json | 2 +- src/pyhf/schemas/1.0.0/measurement.json | 2 +- src/pyhf/schemas/1.0.0/model.json | 2 +- src/pyhf/schemas/1.0.0/patchset.json | 2 +- src/pyhf/schemas/1.0.0/workspace.json | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/pyhf/schemas/1.0.0/defs.json b/src/pyhf/schemas/1.0.0/defs.json index 3f67d9d687..d66e2a1528 100644 --- a/src/pyhf/schemas/1.0.0/defs.json +++ b/src/pyhf/schemas/1.0.0/defs.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-06/schema#", + "$schema": "http://json-schema.org/draft/2020-12/schema#", "$id": "https://scikit-hep.org/pyhf/schemas/1.0.0/defs.json", "definitions": { "workspace": { diff --git a/src/pyhf/schemas/1.0.0/jsonpatch.json b/src/pyhf/schemas/1.0.0/jsonpatch.json index e860625482..e4d83cab9c 100644 --- a/src/pyhf/schemas/1.0.0/jsonpatch.json +++ b/src/pyhf/schemas/1.0.0/jsonpatch.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-06/schema#", + "$schema": "http://json-schema.org/draft/2020-12/schema#", "$id": "https://scikit-hep.org/pyhf/schemas/1.0.0/jsonpatch.json", "$ref": "defs.json#/definitions/jsonpatch" } diff --git a/src/pyhf/schemas/1.0.0/measurement.json b/src/pyhf/schemas/1.0.0/measurement.json index 7ee2c88880..27ce1e3f6d 100644 --- a/src/pyhf/schemas/1.0.0/measurement.json +++ b/src/pyhf/schemas/1.0.0/measurement.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-06/schema#", + "$schema": "http://json-schema.org/draft/2020-12/schema#", "$id": "https://scikit-hep.org/pyhf/schemas/1.0.0/measurement.json", "$ref": "defs.json#/definitions/measurement" } diff --git a/src/pyhf/schemas/1.0.0/model.json b/src/pyhf/schemas/1.0.0/model.json index b3e6c3d44b..37ae8e31e3 100644 --- a/src/pyhf/schemas/1.0.0/model.json +++ b/src/pyhf/schemas/1.0.0/model.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-06/schema#", + "$schema": "http://json-schema.org/draft/2020-12/schema#", "$id": "https://scikit-hep.org/pyhf/schemas/1.0.0/model.json", "$ref": "defs.json#/definitions/model" } diff --git a/src/pyhf/schemas/1.0.0/patchset.json b/src/pyhf/schemas/1.0.0/patchset.json index cf948269b2..620d633ade 100644 --- a/src/pyhf/schemas/1.0.0/patchset.json +++ b/src/pyhf/schemas/1.0.0/patchset.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-06/schema#", + "$schema": "http://json-schema.org/draft/2020-12/schema#", "$id": "https://scikit-hep.org/pyhf/schemas/1.0.0/patchset.json", "$ref": "defs.json#/definitions/patchset" } diff --git a/src/pyhf/schemas/1.0.0/workspace.json b/src/pyhf/schemas/1.0.0/workspace.json index 693d6f1648..d50d231985 100644 --- a/src/pyhf/schemas/1.0.0/workspace.json +++ b/src/pyhf/schemas/1.0.0/workspace.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-06/schema#", + "$schema": "http://json-schema.org/draft/2020-12/schema#", "$id": "https://scikit-hep.org/pyhf/schemas/1.0.0/workspace.json", "$ref": "defs.json#/definitions/workspace" } From 3bb4d0ec8e3be08ce4116b268930c5c0357ae982 Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Wed, 7 Dec 2022 09:49:36 -0800 Subject: [PATCH 31/39] Revert "update draft" This reverts commit 0f10984c72041647fa008062aca4821e7b7dd1c1. --- src/pyhf/schemas/1.0.0/defs.json | 2 +- src/pyhf/schemas/1.0.0/jsonpatch.json | 2 +- src/pyhf/schemas/1.0.0/measurement.json | 2 +- src/pyhf/schemas/1.0.0/model.json | 2 +- src/pyhf/schemas/1.0.0/patchset.json | 2 +- src/pyhf/schemas/1.0.0/workspace.json | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/pyhf/schemas/1.0.0/defs.json b/src/pyhf/schemas/1.0.0/defs.json index d66e2a1528..3f67d9d687 100644 --- a/src/pyhf/schemas/1.0.0/defs.json +++ b/src/pyhf/schemas/1.0.0/defs.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft/2020-12/schema#", + "$schema": "http://json-schema.org/draft-06/schema#", "$id": "https://scikit-hep.org/pyhf/schemas/1.0.0/defs.json", "definitions": { "workspace": { diff --git a/src/pyhf/schemas/1.0.0/jsonpatch.json b/src/pyhf/schemas/1.0.0/jsonpatch.json index e4d83cab9c..e860625482 100644 --- a/src/pyhf/schemas/1.0.0/jsonpatch.json +++ b/src/pyhf/schemas/1.0.0/jsonpatch.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft/2020-12/schema#", + "$schema": "http://json-schema.org/draft-06/schema#", "$id": "https://scikit-hep.org/pyhf/schemas/1.0.0/jsonpatch.json", "$ref": "defs.json#/definitions/jsonpatch" } diff --git a/src/pyhf/schemas/1.0.0/measurement.json b/src/pyhf/schemas/1.0.0/measurement.json index 27ce1e3f6d..7ee2c88880 100644 --- a/src/pyhf/schemas/1.0.0/measurement.json +++ b/src/pyhf/schemas/1.0.0/measurement.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft/2020-12/schema#", + "$schema": "http://json-schema.org/draft-06/schema#", "$id": "https://scikit-hep.org/pyhf/schemas/1.0.0/measurement.json", "$ref": "defs.json#/definitions/measurement" } diff --git a/src/pyhf/schemas/1.0.0/model.json b/src/pyhf/schemas/1.0.0/model.json index 37ae8e31e3..b3e6c3d44b 100644 --- a/src/pyhf/schemas/1.0.0/model.json +++ b/src/pyhf/schemas/1.0.0/model.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft/2020-12/schema#", + "$schema": "http://json-schema.org/draft-06/schema#", "$id": "https://scikit-hep.org/pyhf/schemas/1.0.0/model.json", "$ref": "defs.json#/definitions/model" } diff --git a/src/pyhf/schemas/1.0.0/patchset.json b/src/pyhf/schemas/1.0.0/patchset.json index 620d633ade..cf948269b2 100644 --- a/src/pyhf/schemas/1.0.0/patchset.json +++ b/src/pyhf/schemas/1.0.0/patchset.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft/2020-12/schema#", + "$schema": "http://json-schema.org/draft-06/schema#", "$id": "https://scikit-hep.org/pyhf/schemas/1.0.0/patchset.json", "$ref": "defs.json#/definitions/patchset" } diff --git a/src/pyhf/schemas/1.0.0/workspace.json b/src/pyhf/schemas/1.0.0/workspace.json index d50d231985..693d6f1648 100644 --- a/src/pyhf/schemas/1.0.0/workspace.json +++ b/src/pyhf/schemas/1.0.0/workspace.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft/2020-12/schema#", + "$schema": "http://json-schema.org/draft-06/schema#", "$id": "https://scikit-hep.org/pyhf/schemas/1.0.0/workspace.json", "$ref": "defs.json#/definitions/workspace" } From fcfab823f59b143c724a9767ffc9190d4c7e0233 Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Wed, 7 Dec 2022 10:01:11 -0800 Subject: [PATCH 32/39] downgrade validator to maintain behavior for correct draft for v1.0.0 HiFa --- src/pyhf/schema/validator.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/pyhf/schema/validator.py b/src/pyhf/schema/validator.py index 40094a734f..939ed61e45 100644 --- a/src/pyhf/schema/validator.py +++ b/src/pyhf/schema/validator.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import numbers from pathlib import Path import jsonschema @@ -96,6 +98,10 @@ def validate( Validator = jsonschema.Draft202012Validator + # downgrade Validator for v1.0.0 + if version == '1.0.0': + Validator = jsonschema.Draft6Validator # type: ignore[assignment] + if allow_tensors: type_checker = Validator.TYPE_CHECKER.redefine( "array", _is_array_or_tensor From 862aabb549517d6574453064d45f29fe5a8d1e81 Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Wed, 7 Dec 2022 10:05:56 -0800 Subject: [PATCH 33/39] switch to draft/2020-12 default --- src/pyhf/schema/validator.py | 4 ---- src/pyhf/schemas/1.0.0/defs.json | 2 +- src/pyhf/schemas/1.0.0/jsonpatch.json | 2 +- src/pyhf/schemas/1.0.0/measurement.json | 2 +- src/pyhf/schemas/1.0.0/model.json | 2 +- src/pyhf/schemas/1.0.0/patchset.json | 2 +- src/pyhf/schemas/1.0.0/workspace.json | 2 +- 7 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/pyhf/schema/validator.py b/src/pyhf/schema/validator.py index 939ed61e45..46bdf16177 100644 --- a/src/pyhf/schema/validator.py +++ b/src/pyhf/schema/validator.py @@ -98,10 +98,6 @@ def validate( Validator = jsonschema.Draft202012Validator - # downgrade Validator for v1.0.0 - if version == '1.0.0': - Validator = jsonschema.Draft6Validator # type: ignore[assignment] - if allow_tensors: type_checker = Validator.TYPE_CHECKER.redefine( "array", _is_array_or_tensor diff --git a/src/pyhf/schemas/1.0.0/defs.json b/src/pyhf/schemas/1.0.0/defs.json index 3f67d9d687..d66e2a1528 100644 --- a/src/pyhf/schemas/1.0.0/defs.json +++ b/src/pyhf/schemas/1.0.0/defs.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-06/schema#", + "$schema": "http://json-schema.org/draft/2020-12/schema#", "$id": "https://scikit-hep.org/pyhf/schemas/1.0.0/defs.json", "definitions": { "workspace": { diff --git a/src/pyhf/schemas/1.0.0/jsonpatch.json b/src/pyhf/schemas/1.0.0/jsonpatch.json index e860625482..e4d83cab9c 100644 --- a/src/pyhf/schemas/1.0.0/jsonpatch.json +++ b/src/pyhf/schemas/1.0.0/jsonpatch.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-06/schema#", + "$schema": "http://json-schema.org/draft/2020-12/schema#", "$id": "https://scikit-hep.org/pyhf/schemas/1.0.0/jsonpatch.json", "$ref": "defs.json#/definitions/jsonpatch" } diff --git a/src/pyhf/schemas/1.0.0/measurement.json b/src/pyhf/schemas/1.0.0/measurement.json index 7ee2c88880..27ce1e3f6d 100644 --- a/src/pyhf/schemas/1.0.0/measurement.json +++ b/src/pyhf/schemas/1.0.0/measurement.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-06/schema#", + "$schema": "http://json-schema.org/draft/2020-12/schema#", "$id": "https://scikit-hep.org/pyhf/schemas/1.0.0/measurement.json", "$ref": "defs.json#/definitions/measurement" } diff --git a/src/pyhf/schemas/1.0.0/model.json b/src/pyhf/schemas/1.0.0/model.json index b3e6c3d44b..37ae8e31e3 100644 --- a/src/pyhf/schemas/1.0.0/model.json +++ b/src/pyhf/schemas/1.0.0/model.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-06/schema#", + "$schema": "http://json-schema.org/draft/2020-12/schema#", "$id": "https://scikit-hep.org/pyhf/schemas/1.0.0/model.json", "$ref": "defs.json#/definitions/model" } diff --git a/src/pyhf/schemas/1.0.0/patchset.json b/src/pyhf/schemas/1.0.0/patchset.json index cf948269b2..620d633ade 100644 --- a/src/pyhf/schemas/1.0.0/patchset.json +++ b/src/pyhf/schemas/1.0.0/patchset.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-06/schema#", + "$schema": "http://json-schema.org/draft/2020-12/schema#", "$id": "https://scikit-hep.org/pyhf/schemas/1.0.0/patchset.json", "$ref": "defs.json#/definitions/patchset" } diff --git a/src/pyhf/schemas/1.0.0/workspace.json b/src/pyhf/schemas/1.0.0/workspace.json index 693d6f1648..d50d231985 100644 --- a/src/pyhf/schemas/1.0.0/workspace.json +++ b/src/pyhf/schemas/1.0.0/workspace.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-06/schema#", + "$schema": "http://json-schema.org/draft/2020-12/schema#", "$id": "https://scikit-hep.org/pyhf/schemas/1.0.0/workspace.json", "$ref": "defs.json#/definitions/workspace" } From 6249ffa406e5f9817177cf36a30eb463cd1c4aba Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Thu, 8 Dec 2022 09:31:40 -0800 Subject: [PATCH 34/39] test upgrade 1.0.1 to itself --- tests/test_upgrade.py | 24 +++++++- tests/test_upgrade/patchset_1.0.1.json | 29 +++++++++ tests/test_upgrade/workspace_1.0.1.json | 82 +++++++++++++++++++++++++ 3 files changed, 133 insertions(+), 2 deletions(-) create mode 100644 tests/test_upgrade/patchset_1.0.1.json create mode 100644 tests/test_upgrade/workspace_1.0.1.json diff --git a/tests/test_upgrade.py b/tests/test_upgrade.py index 3c9524c4a6..01adef4305 100644 --- a/tests/test_upgrade.py +++ b/tests/test_upgrade.py @@ -20,7 +20,7 @@ def test_upgrade_to_latest(datadir): def test_1_0_0_workspace(datadir, caplog, monkeypatch): """ - Test upgrading a workspace from 1.0.0 + Test upgrading a workspace from 1.0.0 to 1.0.1 """ spec = json.load(open(datadir.joinpath("workspace_1.0.0.json"), encoding="utf-8")) @@ -37,7 +37,7 @@ def test_1_0_0_workspace(datadir, caplog, monkeypatch): def test_1_0_0_patchset(datadir, caplog, monkeypatch): """ - Test upgrading a patchset from 1.0.0 + Test upgrading a patchset from 1.0.0 to 1.0.1 """ spec = json.load(open(datadir.joinpath("patchset_1.0.0.json"), encoding="utf-8")) @@ -50,3 +50,23 @@ def test_1_0_0_patchset(datadir, caplog, monkeypatch): new_spec = pyhf.schema.upgrade(to_version='1.0.1').patchset(spec) assert new_spec['version'] == '1.0.1' + + +def test_1_0_1_workspace(datadir): + """ + Test upgrading a workspace from 1.0.1 to 1.0.1 + """ + spec = json.load(open(datadir.joinpath("workspace_1.0.1.json"), encoding="utf-8")) + + new_spec = pyhf.schema.upgrade(to_version='1.0.1').workspace(spec) + assert new_spec['version'] == '1.0.1' + + +def test_1_0_1_patchset(datadir): + """ + Test upgrading a patchset from 1.0.1 to 1.0.1 + """ + spec = json.load(open(datadir.joinpath("patchset_1.0.1.json"), encoding="utf-8")) + + new_spec = pyhf.schema.upgrade(to_version='1.0.1').patchset(spec) + assert new_spec['version'] == '1.0.1' diff --git a/tests/test_upgrade/patchset_1.0.1.json b/tests/test_upgrade/patchset_1.0.1.json new file mode 100644 index 0000000000..04dcee3b50 --- /dev/null +++ b/tests/test_upgrade/patchset_1.0.1.json @@ -0,0 +1,29 @@ +{ + "metadata": { + "references": { "hepdata": "ins1234567" }, + "description": "patchset for validation/xmlimport_input/config/example.xml", + "digests": { "sha256": "7c32ca3b8db75cbafcf5cd7ed4672fa2b1fa69e391c9b89068dd947a521866ec" }, + "labels": ["x"] + }, + "patches": [ + { + "metadata": { + "name": "patch_channel1_signal_syst1", + "values": [0] + }, + "patch": [ + { + "op": "replace", + "path": "/channels/0/samples/0/modifiers/0/data/hi", + "value": 1.2 + }, + { + "op": "replace", + "path": "/channels/0/samples/0/modifiers/0/data/lo", + "value": 0.8 + } + ] + } + ], + "version": "1.0.0" +} diff --git a/tests/test_upgrade/workspace_1.0.1.json b/tests/test_upgrade/workspace_1.0.1.json new file mode 100644 index 0000000000..bda01fa940 --- /dev/null +++ b/tests/test_upgrade/workspace_1.0.1.json @@ -0,0 +1,82 @@ +{ + "channels": [ + { + "name": "singlechannel", + "samples": [ + { + "data": [ + 5 + ], + "modifiers": [ + { + "data": null, + "name": "mu", + "type": "normfactor" + } + ], + "name": "signal" + }, + { + "data": [ + 50 + ], + "modifiers": [ + { + "data": [ + 6 + ], + "name": "uncorr_bkguncrt", + "type": "shapesys" + } + ], + "name": "background" + } + ] + } + ], + "measurements": [ + { + "config": { + "parameters": [ + { + "bounds": [ + [ + 0, + 10 + ] + ], + "fixed": false, + "inits": [ + 1 + ], + "name": "mu" + }, + { + "bounds": [ + [ + 1e-10, + 10 + ] + ], + "fixed": false, + "inits": [ + 1 + ], + "name": "uncorr_bkguncrt" + } + ], + "poi": "mu" + }, + "name": "measurement" + } + ], + "observations": [ + { + "data": [ + 50 + ], + "name": "singlechannel" + } + ], + "version": "1.0.0" +} From f05cb95e071943e91b38e256e941322f2b6ec0f2 Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Thu, 2 Feb 2023 10:28:35 -0800 Subject: [PATCH 35/39] fix and use context manager --- pyproject.toml | 2 +- tests/test_upgrade.py | 18 ++++++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index aea6ba02e0..19f4c7b012 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,7 +46,7 @@ classifiers = [ dependencies = [ "click>=8.0.0", # for console scripts "importlib_resources>=1.4.0; python_version < '3.9'", # for resources in schema - "typing_extensions; python_version < '3.11'" # for typing + "typing_extensions; python_version < '3.11'", # for typing "jsonpatch>=1.15", "jsonschema>=4.15.0", # for utils "pyyaml>=5.1", # for parsing CLI equal-delimited options diff --git a/tests/test_upgrade.py b/tests/test_upgrade.py index 01adef4305..5295bcc19e 100644 --- a/tests/test_upgrade.py +++ b/tests/test_upgrade.py @@ -11,10 +11,12 @@ def test_upgrade_bad_version(datadir): def test_upgrade_to_latest(datadir): - ws = json.load(open(datadir.joinpath("workspace_1.0.0.json"), encoding="utf-8")) + with datadir.joinpath("workspace_1.0.0.json").open(encoding="utf-8") as fp: + ws = json.load(fp) pyhf.schema.upgrade().workspace(ws) - ps = json.load(open(datadir.joinpath("workspace_1.0.0.json"), encoding="utf-8")) + with datadir.joinpath("workspace_1.0.0.json").open(encoding="utf-8") as fp: + ps = json.load(fp) pyhf.schema.upgrade().patchset(ps) @@ -22,7 +24,8 @@ def test_1_0_0_workspace(datadir, caplog, monkeypatch): """ Test upgrading a workspace from 1.0.0 to 1.0.1 """ - spec = json.load(open(datadir.joinpath("workspace_1.0.0.json"), encoding="utf-8")) + with datadir.joinpath("workspace_1.0.0.json").open(encoding="utf-8") as fp: + spec = json.load(fp) monkeypatch.setitem(pyhf.schema.versions, 'workspace.json', '1.0.1') with caplog.at_level(logging.INFO, 'pyhf.schema'): @@ -39,7 +42,8 @@ def test_1_0_0_patchset(datadir, caplog, monkeypatch): """ Test upgrading a patchset from 1.0.0 to 1.0.1 """ - spec = json.load(open(datadir.joinpath("patchset_1.0.0.json"), encoding="utf-8")) + with datadir.joinpath("patchset_1.0.0.json").open(encoding="utf-8") as fp: + spec = json.load(fp) monkeypatch.setitem(pyhf.schema.versions, 'patchset.json', '1.0.1') with caplog.at_level(logging.INFO, 'pyhf.schema'): @@ -56,7 +60,8 @@ def test_1_0_1_workspace(datadir): """ Test upgrading a workspace from 1.0.1 to 1.0.1 """ - spec = json.load(open(datadir.joinpath("workspace_1.0.1.json"), encoding="utf-8")) + with datadir.joinpath("workspace_1.0.1.json").open(encoding="utf-8") as fp: + spec = json.load(fp) new_spec = pyhf.schema.upgrade(to_version='1.0.1').workspace(spec) assert new_spec['version'] == '1.0.1' @@ -66,7 +71,8 @@ def test_1_0_1_patchset(datadir): """ Test upgrading a patchset from 1.0.1 to 1.0.1 """ - spec = json.load(open(datadir.joinpath("patchset_1.0.1.json"), encoding="utf-8")) + with datadir.joinpath("patchset_1.0.1.json").open(encoding="utf-8") as fp: + spec = json.load(fp) new_spec = pyhf.schema.upgrade(to_version='1.0.1').patchset(spec) assert new_spec['version'] == '1.0.1' From 531e0badcdf21e20ca0dc0ec92ba19b7c11eaffa Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Thu, 2 Feb 2023 12:37:01 -0800 Subject: [PATCH 36/39] fix coverage --- tests/test_upgrade/patchset_1.0.1.json | 2 +- tests/test_upgrade/workspace_1.0.1.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_upgrade/patchset_1.0.1.json b/tests/test_upgrade/patchset_1.0.1.json index 04dcee3b50..f4518bd0b9 100644 --- a/tests/test_upgrade/patchset_1.0.1.json +++ b/tests/test_upgrade/patchset_1.0.1.json @@ -25,5 +25,5 @@ ] } ], - "version": "1.0.0" + "version": "1.0.1" } diff --git a/tests/test_upgrade/workspace_1.0.1.json b/tests/test_upgrade/workspace_1.0.1.json index bda01fa940..72876811a2 100644 --- a/tests/test_upgrade/workspace_1.0.1.json +++ b/tests/test_upgrade/workspace_1.0.1.json @@ -78,5 +78,5 @@ "name": "singlechannel" } ], - "version": "1.0.0" + "version": "1.0.1" } From bc7fffa3ed6de54868700f54acec6392a9f346ee Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Thu, 7 Dec 2023 15:05:24 +0100 Subject: [PATCH 37/39] initial fix up of typehints --- src/pyhf/schema/__init__.py | 11 ++++--- src/pyhf/schema/validator.py | 63 ++++++++++++++++++++++-------------- src/pyhf/schema/variables.py | 4 ++- 3 files changed, 48 insertions(+), 30 deletions(-) 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/" From e66d252d83c011b9e05a157d2d88fe1fa0a162f4 Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Thu, 7 Dec 2023 15:06:41 +0100 Subject: [PATCH 38/39] fix up --- src/pyhf/schema/validator.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/pyhf/schema/validator.py b/src/pyhf/schema/validator.py index 2261d89d7a..24bde469f9 100644 --- a/src/pyhf/schema/validator.py +++ b/src/pyhf/schema/validator.py @@ -103,10 +103,12 @@ def validate( # design, pathlib strips trailing slashes. See ref below: # * https://bugs.python.org/issue21039 # * https://github.com/python/cpython/issues/65238 + + # for type ignores below, see https://github.com/python-jsonschema/jsonschema/issues/997 resolver = jsonschema.RefResolver( base_uri=f"{path.joinpath(version).as_uri()}/", - referrer=schema_name, - store=variables.SCHEMA_CACHE, + referrer=schema_name, # type: ignore[arg-type] + store=variables.SCHEMA_CACHE, # type: ignore[arg-type] ) Validator = jsonschema.Draft202012Validator From 372c8d2925354eb7a8f127c11f9bc585abff32bf Mon Sep 17 00:00:00 2001 From: Giordon Stark Date: Thu, 7 Dec 2023 15:23:17 +0100 Subject: [PATCH 39/39] don't change draft version for 1.0.0 --- src/pyhf/schemas/1.0.0/defs.json | 2 +- src/pyhf/schemas/1.0.0/jsonpatch.json | 2 +- src/pyhf/schemas/1.0.0/measurement.json | 2 +- src/pyhf/schemas/1.0.0/model.json | 2 +- src/pyhf/schemas/1.0.0/patchset.json | 2 +- src/pyhf/schemas/1.0.0/workspace.json | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/pyhf/schemas/1.0.0/defs.json b/src/pyhf/schemas/1.0.0/defs.json index d66e2a1528..3f67d9d687 100644 --- a/src/pyhf/schemas/1.0.0/defs.json +++ b/src/pyhf/schemas/1.0.0/defs.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft/2020-12/schema#", + "$schema": "http://json-schema.org/draft-06/schema#", "$id": "https://scikit-hep.org/pyhf/schemas/1.0.0/defs.json", "definitions": { "workspace": { diff --git a/src/pyhf/schemas/1.0.0/jsonpatch.json b/src/pyhf/schemas/1.0.0/jsonpatch.json index e4d83cab9c..e860625482 100644 --- a/src/pyhf/schemas/1.0.0/jsonpatch.json +++ b/src/pyhf/schemas/1.0.0/jsonpatch.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft/2020-12/schema#", + "$schema": "http://json-schema.org/draft-06/schema#", "$id": "https://scikit-hep.org/pyhf/schemas/1.0.0/jsonpatch.json", "$ref": "defs.json#/definitions/jsonpatch" } diff --git a/src/pyhf/schemas/1.0.0/measurement.json b/src/pyhf/schemas/1.0.0/measurement.json index 27ce1e3f6d..7ee2c88880 100644 --- a/src/pyhf/schemas/1.0.0/measurement.json +++ b/src/pyhf/schemas/1.0.0/measurement.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft/2020-12/schema#", + "$schema": "http://json-schema.org/draft-06/schema#", "$id": "https://scikit-hep.org/pyhf/schemas/1.0.0/measurement.json", "$ref": "defs.json#/definitions/measurement" } diff --git a/src/pyhf/schemas/1.0.0/model.json b/src/pyhf/schemas/1.0.0/model.json index 37ae8e31e3..b3e6c3d44b 100644 --- a/src/pyhf/schemas/1.0.0/model.json +++ b/src/pyhf/schemas/1.0.0/model.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft/2020-12/schema#", + "$schema": "http://json-schema.org/draft-06/schema#", "$id": "https://scikit-hep.org/pyhf/schemas/1.0.0/model.json", "$ref": "defs.json#/definitions/model" } diff --git a/src/pyhf/schemas/1.0.0/patchset.json b/src/pyhf/schemas/1.0.0/patchset.json index 620d633ade..cf948269b2 100644 --- a/src/pyhf/schemas/1.0.0/patchset.json +++ b/src/pyhf/schemas/1.0.0/patchset.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft/2020-12/schema#", + "$schema": "http://json-schema.org/draft-06/schema#", "$id": "https://scikit-hep.org/pyhf/schemas/1.0.0/patchset.json", "$ref": "defs.json#/definitions/patchset" } diff --git a/src/pyhf/schemas/1.0.0/workspace.json b/src/pyhf/schemas/1.0.0/workspace.json index d50d231985..693d6f1648 100644 --- a/src/pyhf/schemas/1.0.0/workspace.json +++ b/src/pyhf/schemas/1.0.0/workspace.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft/2020-12/schema#", + "$schema": "http://json-schema.org/draft-06/schema#", "$id": "https://scikit-hep.org/pyhf/schemas/1.0.0/workspace.json", "$ref": "defs.json#/definitions/workspace" }