Skip to content
Merged
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
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
# Unreleased

## Enhancements

### Prerelease library schemas can now partner with prerelease standard schemas

Loading a library schema with `withStandard` pointing to a prerelease version of the standard schema (e.g. `withStandard="8.5.0"`) would fail with a `BAD_WITH_STANDARD` error because the `withStandard` partner lookup was always restricted to released schemas, with no way to opt in to prerelease partner resolution.

**Changes:**

- `load_schema()` and `from_string()` in `hed_schema_io.py` now accept a `check_prerelease=False` parameter. When `True`, the `withStandard` partner schema is also searched in the prerelease cache.
- `SchemaLoader` (base class) and all subclasses (`SchemaLoaderXML`, `SchemaLoaderWiki`, `SchemaLoaderJSON`, `SchemaLoaderDF`) accept and forward `check_prerelease`.
- `check_schema_loading.py` (`hed_check_schema_loading` script and `run_loading_check()`) now automatically passes `check_prerelease=True` when loading schemas from a prerelease directory, so `test_all_prerelease_schemas` in `spec_tests` works correctly for library prereleases partnered with a prerelease standard.
- `run_loading_check()` now raises `ValueError` immediately for mutually exclusive flag combinations (`prerelease_only` + `exclude_prereleases`, or `library_filter` + `standard_only`), consistent with the existing CLI-level validation.

# Release 0.9.0 January 22, 2026

The main purpose of this release is to clean up the CLI for the hedtools and to improve the documentation in preparation for release of 1.0.0, which will be a breaking release.
Expand Down
39 changes: 27 additions & 12 deletions hed/schema/hed_schema_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def load_schema_version(xml_version=None, xml_folder=None, check_prerelease=Fals
return _load_schema_version(xml_version=xml_version, xml_folder=xml_folder, check_prerelease=check_prerelease)


def load_schema(hed_path, schema_namespace=None, schema=None, name=None) -> "HedSchema":
def load_schema(hed_path, schema_namespace=None, schema=None, name=None, check_prerelease=False) -> "HedSchema":
"""Load a schema from the given file or URL path.

Parameters:
Expand All @@ -75,6 +75,7 @@ def load_schema(hed_path, schema_namespace=None, schema=None, name=None) -> "Hed
schema (HedSchema or None): A HED schema to merge this new file into
It must be a with-standard schema with the same value.
name (str or None): User supplied identifier for this schema
check_prerelease (bool): If True, allow the partnered standard schema (withStandard) to be a prerelease version.

Returns:
HedSchema: The loaded schema.
Expand All @@ -94,21 +95,23 @@ def load_schema(hed_path, schema_namespace=None, schema=None, name=None) -> "Hed
file_as_string = schema_util.url_to_string(hed_path)
except URLError as e:
raise HedFileError(HedExceptions.URL_ERROR, str(e), hed_path) from e
hed_schema = from_string(file_as_string, schema_format=os.path.splitext(hed_path.lower())[1], name=name)
hed_schema = from_string(
file_as_string, schema_format=os.path.splitext(hed_path.lower())[1], name=name, check_prerelease=check_prerelease
)
elif hed_path.lower().endswith(".xml"):
hed_schema = SchemaLoaderXML.load(hed_path, schema=schema, name=name)
hed_schema = SchemaLoaderXML.load(hed_path, schema=schema, name=name, check_prerelease=check_prerelease)
elif hed_path.lower().endswith(".mediawiki"):
hed_schema = SchemaLoaderWiki.load(hed_path, schema=schema, name=name)
hed_schema = SchemaLoaderWiki.load(hed_path, schema=schema, name=name, check_prerelease=check_prerelease)
elif hed_path.lower().endswith(".json"):
hed_schema = SchemaLoaderJSON.load(hed_path, schema=schema, name=name)
hed_schema = SchemaLoaderJSON.load(hed_path, schema=schema, name=name, check_prerelease=check_prerelease)
elif hed_path.lower().endswith(".tsv") or os.path.isdir(hed_path):
if schema is not None:
raise HedFileError(
HedExceptions.INVALID_HED_FORMAT,
"Cannot pass a schema to merge into spreadsheet loading currently.",
filename=name,
)
hed_schema = SchemaLoaderDF.load_spreadsheet(filenames=hed_path, name=name)
hed_schema = SchemaLoaderDF.load_spreadsheet(filenames=hed_path, name=name, check_prerelease=check_prerelease)
else:
raise HedFileError(HedExceptions.INVALID_EXTENSION, "Unknown schema extension", filename=hed_path)

Expand All @@ -118,7 +121,9 @@ def load_schema(hed_path, schema_namespace=None, schema=None, name=None) -> "Hed
return hed_schema


def from_string(schema_string, schema_format=".xml", schema_namespace=None, schema=None, name=None) -> "HedSchema":
def from_string(
schema_string, schema_format=".xml", schema_namespace=None, schema=None, name=None, check_prerelease=False
) -> "HedSchema":
"""Create a schema from the given string.

Parameters:
Expand All @@ -129,6 +134,7 @@ def from_string(schema_string, schema_format=".xml", schema_namespace=None, sche
schema (HedSchema or None): A HED schema to merge this new file into
It must be a with-standard schema with the same value.
name (str or None): User supplied identifier for this schema
check_prerelease (bool): If True, allow the partnered standard schema (withStandard) to be a prerelease version.

Returns:
HedSchema: The loaded schema.
Expand All @@ -149,11 +155,17 @@ def from_string(schema_string, schema_format=".xml", schema_namespace=None, sche
schema_string = schema_string.replace("\r\n", "\n")

if schema_format.endswith(".xml"):
hed_schema = SchemaLoaderXML.load(schema_as_string=schema_string, schema=schema, name=name)
hed_schema = SchemaLoaderXML.load(
schema_as_string=schema_string, schema=schema, name=name, check_prerelease=check_prerelease
)
elif schema_format.endswith(".mediawiki"):
hed_schema = SchemaLoaderWiki.load(schema_as_string=schema_string, schema=schema, name=name)
hed_schema = SchemaLoaderWiki.load(
schema_as_string=schema_string, schema=schema, name=name, check_prerelease=check_prerelease
)
elif schema_format.endswith(".json"):
hed_schema = SchemaLoaderJSON.load(schema_as_string=schema_string, schema=schema, name=name)
hed_schema = SchemaLoaderJSON.load(
schema_as_string=schema_string, schema=schema, name=name, check_prerelease=check_prerelease
)
else:
raise HedFileError(HedExceptions.INVALID_EXTENSION, f"Unknown schema extension {schema_format}", filename=name)

Expand All @@ -162,14 +174,15 @@ def from_string(schema_string, schema_format=".xml", schema_namespace=None, sche
return hed_schema


def from_dataframes(schema_data, schema_namespace=None, name=None) -> "HedSchema":
def from_dataframes(schema_data, schema_namespace=None, name=None, check_prerelease=False) -> "HedSchema":
"""Create a schema from the given string.

Parameters:
schema_data (dict of str or None): A dict of DF_SUFFIXES:file_as_string_or_df
Should have an entry for all values of DF_SUFFIXES.
schema_namespace (str, None): The name_prefix all tags in this schema will accept.
name (str or None): User supplied identifier for this schema
check_prerelease (bool): If True, allow the partnered standard schema (withStandard) to be a prerelease version.

Returns:
HedSchema: The loaded schema.
Expand All @@ -187,7 +200,9 @@ def from_dataframes(schema_data, schema_namespace=None, name=None) -> "HedSchema
HedExceptions.BAD_PARAMETERS, "Empty or non dict value passed to HedSchema.from_dataframes", filename=name
)

hed_schema = SchemaLoaderDF.load_spreadsheet(schema_as_strings_or_df=schema_data, name=name)
hed_schema = SchemaLoaderDF.load_spreadsheet(
schema_as_strings_or_df=schema_data, name=name, check_prerelease=check_prerelease
)

if schema_namespace:
hed_schema.set_schema_prefix(schema_namespace=schema_namespace)
Expand Down
11 changes: 7 additions & 4 deletions hed/schema/schema_io/base2schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class SchemaLoader(ABC):
SchemaLoaderXML(filename) will load just the header_attributes
"""

def __init__(self, filename, schema_as_string=None, schema=None, file_format=None, name=""):
def __init__(self, filename, schema_as_string=None, schema=None, file_format=None, name="", check_prerelease=False):
"""Loads the given schema from one of the two parameters.

Parameters:
Expand All @@ -30,6 +30,7 @@ def __init__(self, filename, schema_as_string=None, schema=None, file_format=Non
It must be a with-standard schema with the same value.
file_format(str or None): The format of this file if needed(only for owl currently)
name(str or None): Optional user supplied identifier, by default uses filename
check_prerelease(bool): If True, allow the partnered standard schema to be a prerelease version.
"""
if schema_as_string and filename:
raise HedFileError(HedExceptions.BAD_PARAMETERS, "Invalid parameters to schema creation.", filename)
Expand All @@ -38,6 +39,7 @@ def __init__(self, filename, schema_as_string=None, schema=None, file_format=Non
self.name = name if name else filename
self.schema_as_string = schema_as_string
self.appending_to_schema = False
self.check_prerelease = check_prerelease
try:
self.input_data = self._open_file()
except OSError as e:
Expand Down Expand Up @@ -87,7 +89,7 @@ def schema(self):
return self._schema

@classmethod
def load(cls, filename=None, schema_as_string=None, schema=None, file_format=None, name=""):
def load(cls, filename=None, schema_as_string=None, schema=None, file_format=None, name="", check_prerelease=False):
"""Loads and returns the schema, including partnered schema if applicable.

Parameters:
Expand All @@ -98,11 +100,12 @@ def load(cls, filename=None, schema_as_string=None, schema=None, file_format=Non
file_format(str or None): If this is an owl file being loaded, this is the format.
Allowed values include: turtle, json-ld, and owl(xml)
name(str or None): Optional user supplied identifier, by default uses filename
check_prerelease(bool): If True, allow the partnered standard schema to be a prerelease version.

Returns:
HedSchema: The new schema
"""
loader = cls(filename, schema_as_string, schema, file_format, name)
loader = cls(filename, schema_as_string, schema, file_format, name, check_prerelease)
return loader._load()

def _load(self):
Expand All @@ -119,7 +122,7 @@ def _load(self):
saved_attr = self._schema.header_attributes
saved_format = self._schema.source_format
try:
base_version = load_schema_version(self._schema.with_standard)
base_version = load_schema_version(self._schema.with_standard, check_prerelease=self.check_prerelease)
except HedFileError as e:
raise HedFileError(
HedExceptions.BAD_WITH_STANDARD,
Expand Down
9 changes: 5 additions & 4 deletions hed/schema/schema_io/df2schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,30 +22,31 @@ class SchemaLoaderDF(SchemaLoader):
Note: due to supporting multiple files, this one differs from the other schema loaders
"""

def __init__(self, filenames, schema_as_strings_or_df, name=""):
def __init__(self, filenames, schema_as_strings_or_df, name="", check_prerelease=False):
self.filenames = df_util.convert_filenames_to_dict(filenames)
self.schema_as_strings_or_df = schema_as_strings_or_df
if self.filenames:
reported_filename = self.filenames.get(constants.STRUCT_KEY)
else:
reported_filename = "from_strings"
super().__init__(reported_filename, None, None, None, name)
super().__init__(reported_filename, None, None, None, name, check_prerelease)
self._schema.source_format = "spreadsheet"

@classmethod
def load_spreadsheet(cls, filenames=None, schema_as_strings_or_df=None, name=""):
def load_spreadsheet(cls, filenames=None, schema_as_strings_or_df=None, name="", check_prerelease=False):
"""Loads and returns the schema, including partnered schema if applicable.

Parameters:
filenames(str or None or dict of str): A valid set of schema spreadsheet filenames
If a single filename string, assumes the standard filename suffixes.
schema_as_strings_or_df(None or dict of str): A valid set of schema spreadsheet files(tsv as strings)
name (str): what to identify this schema as.
check_prerelease(bool): If True, allow the partnered standard schema to be a prerelease version.

Returns:
HedSchema: The new schema
"""
loader = cls(filenames, schema_as_strings_or_df=schema_as_strings_or_df, name=name)
loader = cls(filenames, schema_as_strings_or_df=schema_as_strings_or_df, name=name, check_prerelease=check_prerelease)
hed_schema = loader._load()
return hed_schema

Expand Down
5 changes: 3 additions & 2 deletions hed/schema/schema_io/json2schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class SchemaLoaderJSON(SchemaLoader):
SchemaLoaderJSON(filename) will load just the header_attributes
"""

def __init__(self, filename, schema_as_string=None, schema=None, file_format=None, name=""):
def __init__(self, filename, schema_as_string=None, schema=None, file_format=None, name="", check_prerelease=False):
"""Initialize the JSON schema loader.

Parameters:
Expand All @@ -26,8 +26,9 @@ def __init__(self, filename, schema_as_string=None, schema=None, file_format=Non
schema (HedSchema or None): A HED schema to merge this new file into
file_format (str or None): Not used for JSON
name (str or None): Optional user supplied identifier, by default uses filename
check_prerelease (bool): If True, allow the partnered standard schema to be a prerelease version.
"""
super().__init__(filename, schema_as_string, schema, file_format, name)
super().__init__(filename, schema_as_string, schema, file_format, name, check_prerelease)
self._json_data = None
self._schema.source_format = ".json"

Expand Down
14 changes: 12 additions & 2 deletions hed/schema/schema_io/wiki2schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,18 @@ class SchemaLoaderWiki(SchemaLoader):
SchemaLoaderWiki(filename) will load just the header_attributes
"""

def __init__(self, filename, schema_as_string=None, schema=None, file_format=None, name=""):
super().__init__(filename, schema_as_string, schema, file_format, name)
def __init__(self, filename, schema_as_string=None, schema=None, file_format=None, name="", check_prerelease=False):
"""Initialize the MediaWiki schema loader.

Parameters:
filename (str or None): A valid filepath or None
schema_as_string (str or None): A full schema as text or None
schema (HedSchema or None): A HED schema to merge this new file into
file_format (str or None): Not used for MediaWiki
name (str or None): Optional user supplied identifier, by default uses filename
check_prerelease (bool): If True, allow the partnered standard schema to be a prerelease version.
"""
super().__init__(filename, schema_as_string, schema, file_format, name, check_prerelease)
self._schema.source_format = ".mediawiki"

def _open_file(self):
Expand Down
14 changes: 12 additions & 2 deletions hed/schema/schema_io/xml2schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,18 @@ class SchemaLoaderXML(SchemaLoader):
SchemaLoaderXML(filename) will load just the header_attributes
"""

def __init__(self, filename, schema_as_string=None, schema=None, file_format=None, name=""):
super().__init__(filename, schema_as_string, schema, file_format, name)
def __init__(self, filename, schema_as_string=None, schema=None, file_format=None, name="", check_prerelease=False):
"""Initialize the XML schema loader.

Parameters:
filename (str or None): A valid filepath or None
schema_as_string (str or None): A full schema as text or None
schema (HedSchema or None): A HED schema to merge this new file into
file_format (str or None): Not used for XML
name (str or None): Optional user supplied identifier, by default uses filename
check_prerelease (bool): If True, allow the partnered standard schema to be a prerelease version.
"""
super().__init__(filename, schema_as_string, schema, file_format, name, check_prerelease)
self._root_element = None
self._parent_map = {}
self._schema.source_format = ".xml"
Expand Down
Loading