Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add with-standard schema header attribute #614

Merged
merged 1 commit into from
Feb 15, 2023
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
10 changes: 5 additions & 5 deletions hed/errors/error_messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@


@hed_tag_error(ValidationErrors.HED_UNITS_INVALID)
def val_error_invalid_unit(tag, unit_class_units):
units_string = ','.join(sorted(unit_class_units))
def val_error_invalid_unit(tag, units):
units_string = ','.join(sorted(units))
return f'Invalid unit - "{tag}" valid units are "{units_string}"', {
"unit_class_units": sorted(unit_class_units)
"units": sorted(units)
}


Expand Down Expand Up @@ -202,8 +202,8 @@ def val_warning_default_units_used(tag, default_unit):
@hed_error(SchemaErrors.HED_SCHEMA_DUPLICATE_NODE)
def schema_error_hed_duplicate_node(tag, duplicate_tag_list, section):
tag_join_delimiter = "\n\t"
return f"Duplicate term '{str(tag)}' used {len(duplicate_tag_list)} places in '{section}' section schema as: " + \
f"{tag_join_delimiter} {tag_join_delimiter.join(duplicate_tag_list)}", {}
return f"Duplicate term '{str(tag)}' used {len(duplicate_tag_list)} places in '{section}' section schema as:" + \
f"{tag_join_delimiter}{tag_join_delimiter.join(duplicate_tag_list)}", {}


@hed_error(SchemaErrors.HED_SCHEMA_ATTRIBUTE_INVALID)
Expand Down
2 changes: 1 addition & 1 deletion hed/errors/error_reporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ def format_error(error_type, *args, actual_error=None, **kwargs):
error_type (str): The type of error for this. Registered with @hed_error or @hed_tag_error.
args (args): Any remaining non keyword args after those required by the error type.
actual_error (str or None): Code to actually add to report out.
kwargs (dict): The other keyword args to pass down to the error handling func.
kwargs (kwargs): The other keyword args to pass down to the error handling func.

Returns:
list: A list containing a single dictionary representing a single error.
Expand Down
2 changes: 1 addition & 1 deletion hed/models/hed_tag.py
Original file line number Diff line number Diff line change
Expand Up @@ -488,7 +488,7 @@ def get_tag_unit_class_units(self):
units = []
unit_classes = self.unit_classes
for unit_class_entry in unit_classes.values():
units += unit_class_entry.unit_class_units.keys()
units += unit_class_entry.units.keys()

return units

Expand Down
170 changes: 115 additions & 55 deletions hed/schema/hed_schema.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@

from hed.schema.hed_schema_constants import HedKey, HedSectionKey
from hed.schema import hed_schema_constants as constants
from hed.schema.schema_io import schema_util
from hed.schema.schema_io.schema2xml import HedSchema2XML
from hed.schema.schema_io.schema2wiki import HedSchema2Wiki
Expand All @@ -9,7 +10,6 @@
from hed.errors import ErrorHandler
from hed.errors.error_types import ValidationErrors


class HedSchema:
""" A HED schema suitable for processing. """

Expand All @@ -20,7 +20,7 @@ def __init__(self):
"""
self._has_duplicate_tags = False
self.header_attributes = {}
self._filename = None
self.filename = None
self.prologue = ""
self.epilogue = ""

Expand All @@ -33,25 +33,6 @@ def __init__(self):
# ===============================================
# Basic schema properties
# ===============================================
@property
def filename(self):
""" The filename if one is known.

Returns:
str: The filename of this schema.
"""
return self._filename

@filename.setter
def filename(self, value):
""" Set the filename, if one has not already been set.

Parameters:
value (str): The source filename for this file
"""
if self._filename is None:
self._filename = value

@property
def version(self):
""" The HED version of this schema.
Expand All @@ -62,27 +43,63 @@ def version(self):
"""
return self.header_attributes['version']

def get_formatted_version(self, as_string=True):
def get_formatted_version(self, as_string=False):
""" The HED version string including prefix and library name if any of this schema.

Returns:
str: The complete version of this schema including library name and prefix.

"""
library = self.header_attributes.get('library', '')
library = self.library
if library:
library = library + '_'
return self._schema_prefix + library + self.header_attributes.get('version', '')
return self._schema_prefix + library + self.version

@property
def library(self):
""" The name of this library schema if one exists.

Returns:
str or None: Library name if any.
str: Library name if any.

"""
return self.header_attributes.get(constants.LIBRARY_ATTRIBUTE, "")

@property
def with_standard(self):
""" The version of the base schema this is extended from, if it exists..

Returns:
str: HED version or ""

"""
return self.header_attributes.get('library')
return self.header_attributes.get(constants.WITH_STANDARD_ATTRIBUTE, "")

@property
def merged(self):
""" Returns if this schema was loaded from a merged file

Returns:
bool: True if file was loaded from a merged file


"""
return self.header_attributes.get(constants.MERGED_ATTRIBUTE, "")

def get_save_header_attributes(self, save_merged=False):
""" returns the attributes that should be saved.

"""
sort_to_start = "!!!!!!!!!!!!!!"
header_attributes = dict(sorted(self.header_attributes.items(), key=lambda x: sort_to_start if x[0] == constants.VERSION_ATTRIBUTE else x[0], reverse=False))
if save_merged:
# make sure it's the last attribute(just to make sure it's in an order)
header_attributes.pop(constants.MERGED_ATTRIBUTE, None)
header_attributes[constants.MERGED_ATTRIBUTE] = "True"
else:
header_attributes.pop(constants.MERGED_ATTRIBUTE, None)

return header_attributes

def schema_for_prefix(self, prefix):
""" Return HedSchema object for this prefix.
Expand Down Expand Up @@ -116,52 +133,63 @@ def valid_prefixes(self):
# ===============================================
# Creation and saving functions
# ===============================================
def get_as_mediawiki_string(self):
def get_as_mediawiki_string(self, save_merged=False):
""" Return the schema to a mediawiki string.

save_merged: bool
If true, this will save the schema as a merged schema if it is a "with-standard" schema.
If it is not a "with-standard" schema, this setting has no effect.
Returns:
str: The schema as a string in mediawiki format.

"""
schema2wiki = HedSchema2Wiki()
output_strings = schema2wiki.process_schema(self)
output_strings = schema2wiki.process_schema(self, save_merged)
return '\n'.join(output_strings)

def get_as_xml_string(self):
def get_as_xml_string(self, save_merged=False):
""" Return the schema to an XML string.

save_merged: bool
If true, this will save the schema as a merged schema if it is a "with-standard" schema.
If it is not a "with-standard" schema, this setting has no effect.
Returns:
str: Return the schema as an XML string.

"""
schema2xml = HedSchema2XML()
xml_tree = schema2xml.process_schema(self)
xml_tree = schema2xml.process_schema(self, save_merged)
return schema_util._xml_element_2_str(xml_tree)

def save_as_xml(self):
""" Save as XML to a temporary file.

Returns:
str: The name of the newly created schema file.

"""
schema2xml = HedSchema2XML()
xml_tree = schema2xml.process_schema(self)
local_xml_file = schema_util.write_xml_tree_2_xml_file(xml_tree, ".xml")
return local_xml_file

def save_as_mediawiki(self):
def save_as_mediawiki(self, save_merged=False):
""" Save as mediawiki to a temporary file.

save_merged: bool
If true, this will save the schema as a merged schema if it is a "with-standard" schema.
If it is not a "with-standard" schema, this setting has no effect.
Returns:
str: The newly created schema filename.

"""
schema2wiki = HedSchema2Wiki()
output_strings = schema2wiki.process_schema(self)
output_strings = schema2wiki.process_schema(self, save_merged)
local_wiki_file = schema_util.write_strings_to_file(output_strings, ".mediawiki")
return local_wiki_file

def save_as_xml(self, save_merged=False):
""" Save as XML to a temporary file.

Returns:
str: The name of the newly created schema file.
save_merged: bool
If true, this will save the schema as a merged schema if it is a "with-standard" schema.
If it is not a "with-standard" schema, this setting has no effect.
"""
schema2xml = HedSchema2XML()
xml_tree = schema2xml.process_schema(self, save_merged)
local_xml_file = schema_util.write_xml_tree_2_xml_file(xml_tree, ".xml")
return local_xml_file

def set_schema_prefix(self, schema_prefix):
""" Set library prefix associated for this schema.

Expand Down Expand Up @@ -260,6 +288,26 @@ def value_classes(self):
"""
return self._sections[HedSectionKey.ValueClasses]

@property
def attributes(self):
""" Return the attributes schema section.

Returns:
HedSchemaSection: The attributes section.

"""
return self._sections[HedSectionKey.Attributes]

@property
def properties(self):
""" Return the properties schema section.

Returns:
HedSchemaSection: The properties section.

"""
return self._sections[HedSectionKey.Properties]

@property
def is_hed3_schema(self):
""" Return true if this is at least version HED3.
Expand Down Expand Up @@ -291,7 +339,7 @@ def __eq__(self, other):
"""
if other is None:
return False
if self.header_attributes != other.header_attributes:
if self.get_save_header_attributes() != other.get_save_header_attributes():
return False
if self._has_duplicate_tags != other._has_duplicate_tags:
return False
Expand All @@ -316,7 +364,7 @@ def __eq__(self, other):
# print(f"{key} not in dict2")
# continue
# if dict1[key] != dict2[key]:
# s = f"{key} unmatched: '{str(dict1[key].long_name)}' vs '{str(dict2[key].long_name)}'"
# s = f"{key} unmatched: '{str(dict1[key].name)}' vs '{str(dict2[key].name)}'"
# print(s)
return False
if self._schema_prefix != other._schema_prefix:
Expand All @@ -338,15 +386,15 @@ def get_unit_class_units(self, unit_class_type):
"""
unit_class_entry = self.get_tag_entry(unit_class_type, HedSectionKey.UnitClasses)
if unit_class_entry:
return unit_class_entry.unit_class_units
return unit_class_entry.units
return []

def get_tags_with_attribute(self, key, section_key=HedSectionKey.AllTags):
""" Return tag entries with the given attribute.

Parameters:
key (str): A tag attribute. Eg HedKey.ExtensionAllowed
section_key (str): The HedSectionKey for the section to retrieve from.
section_key (HedSectionKey): The HedSectionKey for the section to retrieve from.

Returns:
list: A list of all tags with this attribute.
Expand Down Expand Up @@ -511,15 +559,16 @@ def finalize_dictionaries(self):

def _update_all_entries(self):
""" Call finalize_entry on every schema entry(tag, unit, etc). """
for section in self._sections.values():
for key_class, section in self._sections.items():
self._initialize_attributes(key_class)
for entry in section.values():
entry.finalize_entry(self)

def _initialize_attributes(self, key_class):
""" Set the valid attributes for a section.

Parameters:
key_class (str): The section key for the section to update.
key_class (HedSectionKey): The section key for the section to update.

"""
self._sections[key_class].valid_attributes = self._get_attributes_for_section(key_class)
Expand Down Expand Up @@ -573,6 +622,7 @@ def get_all_schema_tags(self, return_last_term=False):
final_list.append(tag_entry.name.split('/')[-1])
else:
final_list.append(tag_entry.name)

return final_list

def get_unknown_attributes(self):
Expand Down Expand Up @@ -674,11 +724,17 @@ def get_modifiers_for_unit(self, unit):
valid_modifiers = self.unit_modifiers.get_entries_with_attribute(modifier_attribute_name)
return valid_modifiers

def _add_element_property_attributes(self, attribute_dict):
attributes = {attribute: entry for attribute, entry in self._sections[HedSectionKey.Attributes].items()
if entry.has_attribute(HedKey.ElementProperty)}

attribute_dict.update(attributes)

def _get_attributes_for_section(self, key_class):
""" Return the valid attributes for this section.

Parameters:
key_class (str): The HedKey for this section.
key_class (HedSectionKey): The HedKey for this section.

Returns:
dict or HedSchemaSection: A dict of all the attributes and this section.
Expand All @@ -687,7 +743,13 @@ def _get_attributes_for_section(self, key_class):
if key_class == HedSectionKey.AllTags:
return self.get_tag_attribute_names()
elif key_class == HedSectionKey.Attributes:
return self._sections[HedSectionKey.Properties]
prop_added_dict = {key:value for key, value in self._sections[HedSectionKey.Properties].items()}
self._add_element_property_attributes(prop_added_dict)
return prop_added_dict
elif key_class == HedSectionKey.Properties:
prop_added_dict = {}
self._add_element_property_attributes(prop_added_dict)
return prop_added_dict
else:
attrib_classes = {
HedSectionKey.UnitClasses: HedKey.UnitClassProperty,
Expand All @@ -700,14 +762,12 @@ def _get_attributes_for_section(self, key_class):
return []

attributes = {attribute: entry for attribute, entry in self._sections[HedSectionKey.Attributes].items()
if entry.has_attribute(attrib_class)}
if entry.has_attribute(attrib_class) or entry.has_attribute(HedKey.ElementProperty)}
return attributes

# ===============================================
# Semi private function used to create a schema in memory(usually from a source file)
# ===============================================
def _add_tag_to_dict(self, long_tag_name, key_class):
section = self._sections[key_class]
if not section:
self._initialize_attributes(key_class)
return section._add_to_dict(long_tag_name)
Loading