Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion tools/schemacode/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
[build-system]
requires = ["pdm-backend", "acres", "pyyaml"]
requires = [
"pdm-backend",
"acres",
"pyyaml",
]
build-backend = "pdm.backend"

[project]
Expand Down Expand Up @@ -52,6 +56,7 @@ tests = [
"coverage[toml]",
"pytest>6",
"pytest-cov",
"requests",
]
all = [
"bidsschematools[tests]",
Expand Down
39 changes: 36 additions & 3 deletions tools/schemacode/src/bidsschematools/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
from copy import deepcopy
from functools import cache, lru_cache
from pathlib import Path
import subprocess
import urllib3
from tempfile import TemporaryDirectory

from . import _lazytypes as lt
from . import data, utils
Expand Down Expand Up @@ -204,7 +207,9 @@ 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
Expand All @@ -218,6 +223,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
-------
Expand All @@ -228,7 +236,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():
Expand All @@ -239,8 +247,33 @@ 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_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:
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():
Expand Down
20 changes: 20 additions & 0 deletions tools/schemacode/src/bidsschematools/tests/test_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
import json
import os
import subprocess
import requests
from collections.abc import Mapping


import pytest
from jsonschema.exceptions import ValidationError

Expand Down Expand Up @@ -47,6 +49,24 @@ def test_load_schema(schema_dir):
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 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."""
for obj_type, obj_type_def in schema_obj["objects"].items():
Expand Down
Loading