From 29c2ed2e4c4aacb64c37dd83657b2758339ab15e Mon Sep 17 00:00:00 2001 From: Anthony Galassi <28850131+bendhouseart@users.noreply.github.com> Date: Fri, 27 Feb 2026 15:02:23 -0500 Subject: [PATCH 1/6] use universal path library for schema retrival --- tools/schemacode/pyproject.toml | 3 +++ .../schemacode/src/bidsschematools/schema.py | 19 +++++++++++++---- .../src/bidsschematools/tests/test_schema.py | 21 +++++++++++++++++++ 3 files changed, 39 insertions(+), 4 deletions(-) diff --git a/tools/schemacode/pyproject.toml b/tools/schemacode/pyproject.toml index 08affa2f79..e20cfc8b4d 100644 --- a/tools/schemacode/pyproject.toml +++ b/tools/schemacode/pyproject.toml @@ -16,6 +16,9 @@ dependencies = [ "acres", "click", "pyyaml", + "universal_pathlib", + "requests", + 'aiohttp', ] classifiers = [ "Development Status :: 4 - Beta", diff --git a/tools/schemacode/src/bidsschematools/schema.py b/tools/schemacode/src/bidsschematools/schema.py index c1055658c3..9b5e149bd1 100644 --- a/tools/schemacode/src/bidsschematools/schema.py +++ b/tools/schemacode/src/bidsschematools/schema.py @@ -8,7 +8,8 @@ from collections.abc import Iterable, Mapping, MutableMapping from copy import deepcopy from functools import cache, lru_cache -from pathlib import Path +from sys import version +from upath import UPath as Path from . import _lazytypes as lt from . import data, utils @@ -204,7 +205,7 @@ def flatten_enums(namespace: Namespace, inplace=True) -> Namespace: @lru_cache -def load_schema(schema_path: lt.Traversable | str | None = None) -> Namespace: +def load_schema(schema_path: lt.Traversable | str | None = None, bids_version:str = None) -> Namespace: """Load the schema into a dict-like structure. This function allows the schema, like BIDS itself, to be specified in @@ -218,6 +219,9 @@ def load_schema(schema_path: lt.Traversable | str | None = None) -> Namespace: schema_path : str, optional Directory containing yaml files or yaml file. If ``None``, use the default schema packaged with ``bidsschematools``. + bids_version: str, optional + Version of bids release to load schema from, allows user to have full namespace/dict + like access to bids schema given a specific version Returns ------- @@ -228,7 +232,7 @@ def load_schema(schema_path: lt.Traversable | str | None = None) -> Namespace: ----- This function is cached, so it will only be called once per schema path. """ - if schema_path is None: + if schema_path is None and bids_version is None: # Default to bundled JSON, fall back to bundled YAML directory schema_path = data.load.readable("schema.json") if not schema_path.is_file(): @@ -239,8 +243,15 @@ def load_schema(schema_path: lt.Traversable | str | None = None) -> Namespace: assert isinstance(schema_path, Path) schema_path = Path.resolve(schema_path.parent / content) lgr.info("No schema path specified, defaulting to the bundled schema, `%s`.", schema_path) - elif isinstance(schema_path, str): + elif isinstance(schema_path, str) and bids_version is None: schema_path = Path(schema_path) + elif schema_path is None and bids_version: + if re.search(r'^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$', bids_version): + schema_path = Path("https://bids-specification.readthedocs.io/en/v{}/schema.json".format(bids_version)) + elif bids_version in ('stable', 'latest'): + schema_path = Path("https://bids-specification.readthedocs.io/en/{}/schema.json".format(bids_version)) + else: + raise Exception(NameError(f"BIDS version is limited to `stable`, `latest`, or semantic format `X.X.X`, you gave {bids_version}")) # JSON file: just load it if schema_path.is_file(): diff --git a/tools/schemacode/src/bidsschematools/tests/test_schema.py b/tools/schemacode/src/bidsschematools/tests/test_schema.py index 0663cec898..15a759c04f 100644 --- a/tools/schemacode/src/bidsschematools/tests/test_schema.py +++ b/tools/schemacode/src/bidsschematools/tests/test_schema.py @@ -3,8 +3,10 @@ import json import os import subprocess +import requests from collections.abc import Mapping + import pytest from jsonschema.exceptions import ValidationError @@ -46,6 +48,25 @@ def test_load_schema(schema_dir): # Check that it is fully dereferenced assert "$ref" not in str(schema_obj) +@pytest.mark.skipif(requests.get('https://bids-specification.readthedocs.io/').status_code != 200, reason="Unable to reach 'https://bids-specification.readthedocs.io, skipping url retrieval") +def test_load_schema_from_url(): + # load from latest release at bids.neuroimaging.io + url = 'https://bids-specification.readthedocs.io/en/latest/schema.json' + schema_obj = schema.load_schema(url) + assert isinstance(schema_obj, Mapping) + + # load using latest and stable keywords + assert isinstance(schema.load_schema(bids_version='latest'), Mapping) + assert isinstance(schema.load_schema(bids_version='stable'), Mapping) + + # load with known version + assert isinstance(schema.load_schema(bids_version='1.11.1'), Mapping) + + # load with bogus url + bad_url = 'https://bids-specification.readthedocs.io/en/not-a-real-version/schema.json' + with pytest.raises(Exception): + schema.load_schema(bad_url) + def test_object_definitions(schema_obj): """Ensure that object definitions in the schema contain required fields.""" From dd113bf227f75b80802cf76b738392d3e3453559 Mon Sep 17 00:00:00 2001 From: Anthony Galassi <28850131+bendhouseart@users.noreply.github.com> Date: Fri, 27 Feb 2026 15:18:23 -0500 Subject: [PATCH 2/6] add upath to build requirements --- tools/schemacode/pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/schemacode/pyproject.toml b/tools/schemacode/pyproject.toml index e20cfc8b4d..1f769d4a4c 100644 --- a/tools/schemacode/pyproject.toml +++ b/tools/schemacode/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["pdm-backend", "acres", "pyyaml"] +requires = ["pdm-backend", "acres", "pyyaml", "universal-pathlib", "requests", "aiohttp"] build-backend = "pdm.backend" [project] @@ -16,7 +16,7 @@ dependencies = [ "acres", "click", "pyyaml", - "universal_pathlib", + "universal-pathlib", "requests", 'aiohttp', ] From 3b5616589c5a458fe502edfc0f35627869e35385 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 27 Feb 2026 20:18:54 +0000 Subject: [PATCH 3/6] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tools/schemacode/pyproject.toml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tools/schemacode/pyproject.toml b/tools/schemacode/pyproject.toml index 1f769d4a4c..bd51801a34 100644 --- a/tools/schemacode/pyproject.toml +++ b/tools/schemacode/pyproject.toml @@ -1,5 +1,12 @@ [build-system] -requires = ["pdm-backend", "acres", "pyyaml", "universal-pathlib", "requests", "aiohttp"] +requires = [ + "pdm-backend", + "acres", + "pyyaml", + "universal-pathlib", + "requests", + "aiohttp", +] build-backend = "pdm.backend" [project] From c4dd320155c0c2916eefbcf1c88fa9692c2dca03 Mon Sep 17 00:00:00 2001 From: Anthony Galassi <28850131+bendhouseart@users.noreply.github.com> Date: Fri, 27 Feb 2026 15:20:28 -0500 Subject: [PATCH 4/6] format with ruff --- .../schemacode/src/bidsschematools/schema.py | 22 ++++++++++++++----- .../src/bidsschematools/tests/test_schema.py | 16 +++++++++----- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/tools/schemacode/src/bidsschematools/schema.py b/tools/schemacode/src/bidsschematools/schema.py index 9b5e149bd1..d9230bcefe 100644 --- a/tools/schemacode/src/bidsschematools/schema.py +++ b/tools/schemacode/src/bidsschematools/schema.py @@ -205,7 +205,9 @@ def flatten_enums(namespace: Namespace, inplace=True) -> Namespace: @lru_cache -def load_schema(schema_path: lt.Traversable | str | None = None, bids_version:str = None) -> Namespace: +def load_schema( + schema_path: lt.Traversable | str | None = None, bids_version: str = None +) -> Namespace: """Load the schema into a dict-like structure. This function allows the schema, like BIDS itself, to be specified in @@ -246,12 +248,20 @@ def load_schema(schema_path: lt.Traversable | str | None = None, bids_version:st elif isinstance(schema_path, str) and bids_version is None: schema_path = Path(schema_path) elif schema_path is None and bids_version: - if re.search(r'^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$', bids_version): - schema_path = Path("https://bids-specification.readthedocs.io/en/v{}/schema.json".format(bids_version)) - elif bids_version in ('stable', 'latest'): - schema_path = Path("https://bids-specification.readthedocs.io/en/{}/schema.json".format(bids_version)) + if re.search(r"^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$", bids_version): + schema_path = Path( + "https://bids-specification.readthedocs.io/en/v{}/schema.json".format(bids_version) + ) + elif bids_version in ("stable", "latest"): + schema_path = Path( + "https://bids-specification.readthedocs.io/en/{}/schema.json".format(bids_version) + ) else: - raise Exception(NameError(f"BIDS version is limited to `stable`, `latest`, or semantic format `X.X.X`, you gave {bids_version}")) + raise Exception( + NameError( + f"BIDS version is limited to `stable`, `latest`, or semantic format `X.X.X`, you gave {bids_version}" + ) + ) # JSON file: just load it if schema_path.is_file(): diff --git a/tools/schemacode/src/bidsschematools/tests/test_schema.py b/tools/schemacode/src/bidsschematools/tests/test_schema.py index 15a759c04f..deedc12732 100644 --- a/tools/schemacode/src/bidsschematools/tests/test_schema.py +++ b/tools/schemacode/src/bidsschematools/tests/test_schema.py @@ -48,22 +48,26 @@ def test_load_schema(schema_dir): # Check that it is fully dereferenced assert "$ref" not in str(schema_obj) -@pytest.mark.skipif(requests.get('https://bids-specification.readthedocs.io/').status_code != 200, reason="Unable to reach 'https://bids-specification.readthedocs.io, skipping url retrieval") + +@pytest.mark.skipif( + requests.get("https://bids-specification.readthedocs.io/").status_code != 200, + reason="Unable to reach 'https://bids-specification.readthedocs.io, skipping url retrieval", +) def test_load_schema_from_url(): # load from latest release at bids.neuroimaging.io - url = 'https://bids-specification.readthedocs.io/en/latest/schema.json' + url = "https://bids-specification.readthedocs.io/en/latest/schema.json" schema_obj = schema.load_schema(url) assert isinstance(schema_obj, Mapping) # load using latest and stable keywords - assert isinstance(schema.load_schema(bids_version='latest'), Mapping) - assert isinstance(schema.load_schema(bids_version='stable'), Mapping) + assert isinstance(schema.load_schema(bids_version="latest"), Mapping) + assert isinstance(schema.load_schema(bids_version="stable"), Mapping) # load with known version - assert isinstance(schema.load_schema(bids_version='1.11.1'), Mapping) + assert isinstance(schema.load_schema(bids_version="1.11.1"), Mapping) # load with bogus url - bad_url = 'https://bids-specification.readthedocs.io/en/not-a-real-version/schema.json' + bad_url = "https://bids-specification.readthedocs.io/en/not-a-real-version/schema.json" with pytest.raises(Exception): schema.load_schema(bad_url) From 3961087fc7a9055f752d9aec868d7f23ccf254ca Mon Sep 17 00:00:00 2001 From: Anthony Galassi <28850131+bendhouseart@users.noreply.github.com> Date: Fri, 27 Feb 2026 16:47:54 -0500 Subject: [PATCH 5/6] native libraries only --- tools/schemacode/pyproject.toml | 7 +---- .../schemacode/src/bidsschematools/schema.py | 28 +++++++++++++------ .../src/bidsschematools/tests/test_schema.py | 11 ++------ 3 files changed, 24 insertions(+), 22 deletions(-) diff --git a/tools/schemacode/pyproject.toml b/tools/schemacode/pyproject.toml index bd51801a34..9a055a1493 100644 --- a/tools/schemacode/pyproject.toml +++ b/tools/schemacode/pyproject.toml @@ -3,9 +3,6 @@ requires = [ "pdm-backend", "acres", "pyyaml", - "universal-pathlib", - "requests", - "aiohttp", ] build-backend = "pdm.backend" @@ -23,9 +20,6 @@ dependencies = [ "acres", "click", "pyyaml", - "universal-pathlib", - "requests", - 'aiohttp', ] classifiers = [ "Development Status :: 4 - Beta", @@ -62,6 +56,7 @@ tests = [ "coverage[toml]", "pytest>6", "pytest-cov", + "requests", ] all = [ "bidsschematools[tests]", diff --git a/tools/schemacode/src/bidsschematools/schema.py b/tools/schemacode/src/bidsschematools/schema.py index d9230bcefe..24e81a2ae1 100644 --- a/tools/schemacode/src/bidsschematools/schema.py +++ b/tools/schemacode/src/bidsschematools/schema.py @@ -8,8 +8,10 @@ from collections.abc import Iterable, Mapping, MutableMapping from copy import deepcopy from functools import cache, lru_cache -from sys import version -from upath import UPath as Path +from pathlib import Path +import subprocess +import urllib3 +from tempfile import TemporaryDirectory from . import _lazytypes as lt from . import data, utils @@ -249,19 +251,29 @@ def load_schema( schema_path = Path(schema_path) elif schema_path is None and bids_version: if re.search(r"^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$", bids_version): - schema_path = Path( - "https://bids-specification.readthedocs.io/en/v{}/schema.json".format(bids_version) - ) + schema_url = "https://bids-specification.readthedocs.io/en/v{}/schema.json".format(bids_version) + elif bids_version in ("stable", "latest"): - schema_path = Path( - "https://bids-specification.readthedocs.io/en/{}/schema.json".format(bids_version) - ) + schema_url = "https://bids-specification.readthedocs.io/en/{}/schema.json".format(bids_version) else: raise Exception( NameError( f"BIDS version is limited to `stable`, `latest`, or semantic format `X.X.X`, you gave {bids_version}" ) ) + http = urllib3.PoolManager() + response = http.request("GET", schema_url) + if response.status != 200: + raise urllib3.exceptions.HTTPError( + f"Unable to retrieve schema from {schema_url} (status {response.status})" + ) + else: + with TemporaryDirectory() as tmp: + schema_path = Path(tmp) / "schema.json" + with open(schema_path, 'w') as f: + json.dump(response.json(), f) + + return Namespace.from_json(schema_path.read_text()) # JSON file: just load it if schema_path.is_file(): diff --git a/tools/schemacode/src/bidsschematools/tests/test_schema.py b/tools/schemacode/src/bidsschematools/tests/test_schema.py index deedc12732..984c805d4e 100644 --- a/tools/schemacode/src/bidsschematools/tests/test_schema.py +++ b/tools/schemacode/src/bidsschematools/tests/test_schema.py @@ -54,11 +54,6 @@ def test_load_schema(schema_dir): reason="Unable to reach 'https://bids-specification.readthedocs.io, skipping url retrieval", ) def test_load_schema_from_url(): - # load from latest release at bids.neuroimaging.io - url = "https://bids-specification.readthedocs.io/en/latest/schema.json" - schema_obj = schema.load_schema(url) - assert isinstance(schema_obj, Mapping) - # load using latest and stable keywords assert isinstance(schema.load_schema(bids_version="latest"), Mapping) assert isinstance(schema.load_schema(bids_version="stable"), Mapping) @@ -67,9 +62,9 @@ def test_load_schema_from_url(): assert isinstance(schema.load_schema(bids_version="1.11.1"), Mapping) # load with bogus url - bad_url = "https://bids-specification.readthedocs.io/en/not-a-real-version/schema.json" - with pytest.raises(Exception): - schema.load_schema(bad_url) + #bad_url = "https://bids-specification.readthedocs.io/en/not-a-real-version/schema.json" + #with pytest.raises(Exception): + # schema.load_schema(bad_url) def test_object_definitions(schema_obj): From 8facf010d03b440317c2840523a594e4166e506a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 27 Feb 2026 21:48:53 +0000 Subject: [PATCH 6/6] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tools/schemacode/src/bidsschematools/schema.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/schemacode/src/bidsschematools/schema.py b/tools/schemacode/src/bidsschematools/schema.py index 24e81a2ae1..bbf79305af 100644 --- a/tools/schemacode/src/bidsschematools/schema.py +++ b/tools/schemacode/src/bidsschematools/schema.py @@ -252,7 +252,7 @@ def load_schema( elif schema_path is None and bids_version: if re.search(r"^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$", bids_version): schema_url = "https://bids-specification.readthedocs.io/en/v{}/schema.json".format(bids_version) - + elif bids_version in ("stable", "latest"): schema_url = "https://bids-specification.readthedocs.io/en/{}/schema.json".format(bids_version) else: @@ -272,7 +272,7 @@ def load_schema( schema_path = Path(tmp) / "schema.json" with open(schema_path, 'w') as f: json.dump(response.json(), f) - + return Namespace.from_json(schema_path.read_text()) # JSON file: just load it