Repository: hed-standard/hed-python (language: python)

Issue:
Add requested_columns to column mapper.  Only check specific columns for definitions



Discussion / hints:

Fix patch:
diff --git a/.codeclimate.yml b/.codeclimate.yml
index 44291abb9..78f191df8 100644
--- a/.codeclimate.yml
+++ b/.codeclimate.yml
@@ -60,12 +60,14 @@ exclude_patterns:
 - "**/spec/"
 - "**/test/"
 - "**/tests/"
+- "**/docs/"
 - "Tests/"
 - "**/vendor/"
 - "**/*_test.go"
 - "**/*.d.ts"
 - "**/*__init__.py"
 - "**/*setup.py"
+- "**/setup.cfg"
 - "**/static/"
 - "**/constants/"
 - "**/deploy_hed/"
@@ -80,9 +82,10 @@ exclude_patterns:
 - "**/*.html"
 - "**/.coveragerc"
 - "**/*.md"
-- "**/app_factory.py"
-- "**/runserver.py"
 - "**/*.rst"
 - "**/templates/"
 - "**/conf.py"
-- "**/deprecated/"
\ No newline at end of file
+- "**/deprecated/"
+- "**/versioneer.py"
+- "**/_version.py"
+- "**/cli/"
\ No newline at end of file
diff --git a/hed/cli/docker_file b/hed/cli/docker_file
deleted file mode 100644
index a9349c202..000000000
--- a/hed/cli/docker_file
+++ /dev/null
@@ -1,179 +0,0 @@
-FROM ubuntu:focal-20220426
-
-# Prepare environment
-RUN apt-get update && \
-    apt-get install -y --no-install-recommends \
-                    apt-utils \
-                    autoconf \
-                    build-essential \
-                    bzip2 \
-                    ca-certificates \
-                    curl \
-                    git \
-                    libtool \
-                    lsb-release \
-                    netbase \
-                    pkg-config \
-                    unzip \
-                    xvfb && \
-    apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
-
-ENV DEBIAN_FRONTEND="noninteractive" \
-    LANG="en_US.UTF-8" \
-    LC_ALL="en_US.UTF-8"
-
-"
-
-# AFNI latest (neurodocker build)
-RUN apt-get update -qq \
-    && apt-get install -y -q --no-install-recommends \
-           apt-utils \
-           ed \
-           gsl-bin \
-           libglib2.0-0 \
-           libglu1-mesa-dev \
-           libglw1-mesa \
-           libgomp1 \
-           libjpeg62 \
-           libxm4 \
-           netpbm \
-           tcsh \
-           xfonts-base \
-           xvfb \
-    && apt-get clean \
-    && rm -rf /var/lib/apt/lists/* \
-    && curl -sSL --retry 5 -o /tmp/multiarch.deb http://archive.ubuntu.com/ubuntu/pool/main/g/glibc/multiarch-support_2.27-3ubuntu1.5_amd64.deb \
-    && dpkg -i /tmp/multiarch.deb \
-    && rm /tmp/multiarch.deb \
-    && curl -sSL --retry 5 -o /tmp/libxp6.deb http://mirrors.kernel.org/debian/pool/main/libx/libxp/libxp6_1.0.2-2_amd64.deb \
-    && dpkg -i /tmp/libxp6.deb \
-    && rm /tmp/libxp6.deb \
-    && curl -sSL --retry 5 -o /tmp/libpng.deb http://snapshot.debian.org/archive/debian-security/20160113T213056Z/pool/updates/main/libp/libpng/libpng12-0_1.2.49-1%2Bdeb7u2_amd64.deb \
-    && dpkg -i /tmp/libpng.deb \
-    && rm /tmp/libpng.deb \
-    && apt-get install -f \
-    && apt-get clean \
-    && rm -rf /var/lib/apt/lists/* \
-    && gsl2_path="$(find / -name 'libgsl.so.19' || printf '')" \
-    && if [ -n "$gsl2_path" ]; then \
-         ln -sfv "$gsl2_path" "$(dirname $gsl2_path)/libgsl.so.0"; \
-    fi \
-    && ldconfig \
-    && echo "Downloading AFNI ..." \
-    && mkdir -p /opt/afni-latest \
-    && curl -fsSL --retry 5 https://afni.nimh.nih.gov/pub/dist/tgz/linux_openmp_64.tgz \
-    | tar -xz -C /opt/afni-latest --strip-components 1 \
-    --exclude "linux_openmp_64/*.gz" \
-    --exclude "linux_openmp_64/funstuff" \
-    --exclude "linux_openmp_64/shiny" \
-    --exclude "linux_openmp_64/afnipy" \
-    --exclude "linux_openmp_64/lib/RetroTS" \
-    --exclude "linux_openmp_64/meica.libs" \
-    # Keep only what we use
-    && find /opt/afni-latest -type f -not \( \
-        -name "3dTshift" -or \
-        -name "3dUnifize" -or \
-        -name "3dAutomask" -or \
-        -name "3dvolreg" \) -delete
-
-ENV PATH="/opt/afni-latest:$PATH" \
-    AFNI_IMSAVE_WARNINGS="NO" \
-    AFNI_PLUGINPATH="/opt/afni-latest"
-
-# Installing ANTs 2.3.3 (NeuroDocker build)
-# Note: the URL says 2.3.4 but it is actually 2.3.3
-ENV ANTSPATH="/opt/ants" \
-    PATH="/opt/ants:$PATH"
-WORKDIR $ANTSPATH
-RUN curl -sSL "https://dl.dropbox.com/s/gwf51ykkk5bifyj/ants-Linux-centos6_x86_64-v2.3.4.tar.gz" \
-    | tar -xzC $ANTSPATH --strip-components 1
-
-# Installing and setting up ICA_AROMA
-WORKDIR /opt/ICA-AROMA
-RUN curl -sSL "https://github.com/oesteban/ICA-AROMA/archive/v0.4.5.tar.gz" \
-  | tar -xzC /opt/ICA-AROMA --strip-components 1 && \
-  chmod +x /opt/ICA-AROMA/ICA_AROMA.py
-ENV PATH="/opt/ICA-AROMA:$PATH" \
-    AROMA_VERSION="0.4.5"
-
-WORKDIR /opt
-RUN curl -sSLO https://www.humanconnectome.org/storage/app/media/workbench/workbench-linux64-v1.5.0.zip && \
-    unzip workbench-linux64-v1.5.0.zip && \
-    rm workbench-linux64-v1.5.0.zip && \
-    rm -rf /opt/workbench/libs_linux64_software_opengl /opt/workbench/plugins_linux64 && \
-    strip --remove-section=.note.ABI-tag /opt/workbench/libs_linux64/libQt5Core.so.5
-    # ABI tags can interfere when running on Singularity
-
-ENV PATH="/opt/workbench/bin_linux64:$PATH" \
-    LD_LIBRARY_PATH="/opt/workbench/lib_linux64:$LD_LIBRARY_PATH"
-
-# nipreps/miniconda:py39_4.12.0rc0
-COPY --from=nipreps/miniconda@sha256:5aa4d2bb46e7e56fccf6e93ab3ff765add74e79f96ffa00449504b4869790cb9 /opt/conda /opt/conda
-
-RUN ln -s /opt/conda/etc/profile.d/conda.sh /etc/profile.d/conda.sh && \
-    echo ". /opt/conda/etc/profile.d/conda.sh" >> ~/.bashrc && \
-    echo "conda activate base" >> ~/.bashrc
-
-# Set CPATH for packages relying on compiled libs (e.g. indexed_gzip)
-ENV PATH="/opt/conda/bin:$PATH" \
-    CPATH="/opt/conda/include:$CPATH" \
-    LD_LIBRARY_PATH="/opt/conda/lib:$LD_LIBRARY_PATH" \
-    LANG="C.UTF-8" \
-    LC_ALL="C.UTF-8" \
-    PYTHONNOUSERSITE=1
-
-# Unless otherwise specified each process should only use one thread - nipype
-# will handle parallelization
-ENV MKL_NUM_THREADS=1 \
-    OMP_NUM_THREADS=1
-
-# Create a shared $HOME directory
-RUN useradd -m -s /bin/bash -G users fmriprep
-WORKDIR /home/fmriprep
-ENV HOME="/home/fmriprep" \
-    LD_LIBRARY_PATH="/usr/lib/x86_64-linux-gnu:$LD_LIBRARY_PATH"
-
-RUN echo ". /opt/conda/etc/profile.d/conda.sh" >> $HOME/.bashrc && \
-    echo "conda activate base" >> $HOME/.bashrc
-
-# Precaching atlases
-COPY scripts/fetch_templates.py fetch_templates.py
-
-RUN /opt/conda/bin/python fetch_templates.py && \
-    rm fetch_templates.py && \
-    find $HOME/.cache/templateflow -type d -exec chmod go=u {} + && \
-    find $HOME/.cache/templateflow -type f -exec chmod go=u {} +
-
-# Installing FMRIPREP
-COPY . /src/fmriprep
-ARG VERSION
-# Force static versioning within container
-RUN echo "${VERSION}" > /src/fmriprep/fmriprep/VERSION && \
-    echo "include fmriprep/VERSION" >> /src/fmriprep/MANIFEST.in && \
-    /opt/conda/bin/python -m pip install --no-cache-dir "/src/fmriprep[all]"
-
-RUN find $HOME -type d -exec chmod go=u {} + && \
-    find $HOME -type f -exec chmod go=u {} + && \
-    rm -rf $HOME/.npm $HOME/.conda $HOME/.empty
-
-# HACK for FreeSurfer 7.2.0
-# Fixed in https://github.com/freesurfer/freesurfer/pull/886, so remove on release
-ENV FREESURFER="/opt/freesurfer"
-
-ENV IS_DOCKER_8395080871=1
-
-RUN ldconfig
-WORKDIR /tmp
-ENTRYPOINT ["/opt/conda/bin/fmriprep"]
-
-ARG BUILD_DATE
-ARG VCS_REF
-ARG VERSION
-LABEL org.label-schema.build-date=$BUILD_DATE \
-      org.label-schema.name="fMRIPrep" \
-      org.label-schema.description="fMRIPrep - robust fMRI preprocessing tool" \
-      org.label-schema.url="http://fmriprep.org" \
-      org.label-schema.vcs-ref=$VCS_REF \
-      org.label-schema.vcs-url="https://github.com/nipreps/fmriprep" \
-      org.label-schema.version=$VERSION \
-      org.label-schema.schema-version="1.0"
diff --git a/hed/models/base_input.py b/hed/models/base_input.py
index 187d27dfb..dacd2ad24 100644
--- a/hed/models/base_input.py
+++ b/hed/models/base_input.py
@@ -28,7 +28,7 @@ class BaseInput:
     COMMA_DELIMITER = ','
 
     def __init__(self, file, file_type=None, worksheet_name=None, has_column_names=True, mapper=None, def_mapper=None,
-                 name=None):
+                 definition_columns=None, name=None):
         """ Constructor for the BaseInput class.
 
         Args:
@@ -39,6 +39,7 @@ def __init__(self, file, file_type=None, worksheet_name=None, has_column_names=T
                 (Not applicable to tsv files.)
             has_column_names (bool): True if file has column names.
             mapper (ColumnMapper or None):  Indicates which columns have HED tags.
+            definition_columns(list or None): A list of columns to check for definitions.  Explicit 'None' means all.
             name (str or None): Optional field for how this file will report errors.
 
         Notes:
@@ -54,6 +55,7 @@ def __init__(self, file, file_type=None, worksheet_name=None, has_column_names=T
         # This is the loaded workbook if we loaded originally from an excel file.
         self._loaded_workbook = None
         self._worksheet_name = worksheet_name
+        self._def_columns = definition_columns
         self.file_def_dict = None
         pandas_header = 0
         if not self._has_column_names:
@@ -234,43 +236,34 @@ def to_csv(self, file=None, output_processed_file=False):
                                                                     header=output_file._has_column_names)
         return csv_string_if_filename_none
 
-    def __iter__(self):
-        """ Iterate over the underlying dataframe. """
-        return self.iter_dataframe()
-
-    def iter_raw(self, hed_ops=None, error_handler=None, **kwargs):
-        """ Iterate all columns without substitutions
-
-        Args:
-            hed_ops (list, func, HedOps, or None): A func, a HedOps or a list of these to apply to the
-                hed strings before returning.
-            error_handler (ErrorHandler or None): Handler to use for context or a default one if None.
-            kwargs:
-
-        Yields:
-            - dict: A dict with column_number keys and values corresponding to the cell at that position.
+    @property
+    def columns(self):
+        """ Returns a list of the column names.
 
-        Notes:
-            - See models.hed_ops.translate_ops or the specific hed_ops for additional options.
-            - Primarily for altering or re-saving the original file (e.g., convert short tags to long).
-            - Used for initial processing when trying to find definitions.
+            Empty if no column names.
 
+        Returns:
+            columns(list): The column names.
         """
-        if error_handler is None:
-            error_handler = ErrorHandler()
+        columns = []
+        if self._dataframe is not None and self._has_column_names:
+            columns = list(self._dataframe.columns)
+        return columns
 
-        default_mapper = ColumnMapper()
-        return self.iter_dataframe(hed_ops=hed_ops, mapper=default_mapper, run_string_ops_on_columns=True,
-                                   error_handler=error_handler, **kwargs)
+    def __iter__(self):
+        """ Iterate over the underlying dataframe. """
+        return self.iter_dataframe()
 
-    def iter_dataframe(self, hed_ops=None, mapper=None, return_string_only=True, run_string_ops_on_columns=False,
-                       error_handler=None, expand_defs=False, remove_definitions=True, **kwargs):
+    def iter_dataframe(self, hed_ops=None, mapper=None, requested_columns=None, return_string_only=True,
+                       run_string_ops_on_columns=False, error_handler=None, expand_defs=False, remove_definitions=True,
+                       **kwargs):
         """ Iterate rows based on the given column mapper.
 
         Args:
             hed_ops (list, func, HedOps, or None):  A func, a HedOps or a list of these to apply to the
                                                     hed strings before returning.
             mapper (ColumnMapper or None): The column name to column number mapper (or internal mapper if None).
+            requested_columns(list or None): If this is not None, return ONLY these columns.  Names or numbers allowed.
             return_string_only (bool): If True, do not return issues list, individual columns, attribute columns, etc.
             run_string_ops_on_columns (bool):   If true, run all tag and string ops on columns,
                                                 rather than columns then rows.
@@ -289,6 +282,11 @@ def iter_dataframe(self, hed_ops=None, mapper=None, return_string_only=True, run
         if mapper is None:
             mapper = self._mapper
 
+        if requested_columns:
+            # Make a copy to ensure we don't alter the actual mapper
+            mapper = copy.deepcopy(mapper)
+            mapper.set_requested_columns(requested_columns)
+
         tag_funcs, string_funcs = self._translate_ops(hed_ops, run_string_ops_on_columns=run_string_ops_on_columns,
                                                       expand_defs=expand_defs, remove_definitions=remove_definitions,
                                                       error_handler=error_handler, **kwargs)
@@ -438,20 +436,13 @@ def _get_dataframe_from_worksheet(worksheet, has_headers):
         else:
             return pandas.DataFrame(worksheet.values, dtype=str)
 
-    def _run_validators(self, hed_ops, error_handler, run_on_raw=False, expand_defs=False, **kwargs):
+    def _run_validators(self, hed_ops, error_handler, expand_defs=False, **kwargs):
         validation_issues = []
-        if run_on_raw:
-            for row_dict in self.iter_raw(hed_ops=hed_ops,
-                                          return_string_only=False,
-                                          error_handler=error_handler, expand_defs=expand_defs,
-                                          **kwargs):
-                validation_issues += row_dict[model_constants.ROW_ISSUES]
-        else:
-            for row_dict in self.iter_dataframe(hed_ops=hed_ops,
-                                                return_string_only=False,
-                                                error_handler=error_handler, expand_defs=expand_defs,
-                                                **kwargs):
-                validation_issues += row_dict[model_constants.ROW_ISSUES]
+        for row_dict in self.iter_dataframe(hed_ops=hed_ops,
+                                            return_string_only=False,
+                                            error_handler=error_handler, expand_defs=expand_defs,
+                                            **kwargs):
+            validation_issues += row_dict[model_constants.ROW_ISSUES]
 
         return validation_issues
 
@@ -529,7 +520,14 @@ def extract_definitions(self, error_handler=None):
             error_handler = ErrorHandler()
         new_def_dict = DefinitionDict()
         hed_ops = [new_def_dict]
-        _ = self._run_validators(hed_ops, run_on_raw=True, error_handler=error_handler)
+        for _ in self.iter_dataframe(hed_ops=hed_ops,
+                                     return_string_only=False,
+                                     requested_columns=self._def_columns,
+                                     run_string_ops_on_columns=True,
+                                     remove_definitions=False,
+                                     error_handler=error_handler):
+            pass
+
         return new_def_dict
 
     def update_definition_mapper(self, def_dict):
diff --git a/hed/models/column_mapper.py b/hed/models/column_mapper.py
index 82cd98b50..9c11568ff 100644
--- a/hed/models/column_mapper.py
+++ b/hed/models/column_mapper.py
@@ -17,14 +17,12 @@ class ColumnMapper:
     Notes:
         - Functions and variables column and row indexing starts at 0.
     """
-    def __init__(self, sidecars=None, tag_columns=None, column_prefix_dictionary=None,
-                 attribute_columns=None, optional_tag_columns=None):
+    def __init__(self, sidecar=None, tag_columns=None, column_prefix_dictionary=None,
+                 attribute_columns=None, optional_tag_columns=None, requested_columns=None):
         """ Constructor for ColumnMapper.
 
         Args:
-            sidecars (Sidecar, string, or list of these): A list of Sidecars or
-                 filenames to gather ColumnDefinitions from.
-                Sidecars later in the list override those earlier in the list.
+            sidecar (Sidecar): A sidecar to gather column data from.
             tag_columns: (list):  A list of ints or strings containing the columns that contain the HED tags.
                 Sidecar column definitions will take precedent if there is a conflict with tag_columns.
             column_prefix_dictionary (dict): Dictionary with keys that are column numbers and values are HED tag
@@ -47,37 +45,44 @@ def __init__(self, sidecars=None, tag_columns=None, column_prefix_dictionary=Non
         self.column_data = {}
         # Maps column number to column_entry.  This is what's actually used by most code.
         self._final_column_map = {}
+        self._no_mapping_info = True
 
         self._column_map = None
+        self._requested_columns = []
         self._tag_columns = []
         self._optional_tag_columns = []
         self._column_prefix_dictionary = {}
 
         self._na_patterns = ["n/a", "nan"]
         self._finalize_mapping_issues = []
-        self._has_sidecars = False
-        if sidecars:
-            self.add_sidecars(sidecars)
+        self._sidecar = None
+        self._set_sidecar(sidecar)
         self.add_columns(attribute_columns)
 
+        self.set_requested_columns(requested_columns, False)
         self.set_tag_columns(tag_columns, optional_tag_columns, False)
         self.set_column_prefix_dict(column_prefix_dictionary, False)
 
         # finalize the column map based on initial settings with no header
         self._finalize_mapping()
 
-    def add_sidecars(self, sidecars):
-        """ Add sidecar column info.
+    def _set_sidecar(self, sidecar):
+        """ Set the sidecar this column mapper uses
 
         Args:
-            sidecars (list): A list of filenames or loaded sidecar files in any mix.
+            sidecar (Sidecar or None): the sidecar to use
+
+        Returns:
 
         """
-        self._has_sidecars = True
-        sidecars = Sidecar.load_multiple_sidecars(sidecars)
-        for sidecar in sidecars:
-            for column_data in sidecar:
-                self._add_column_data(column_data)
+        if self._sidecar:
+            raise ValueError("Trying to set a second sidecar on a column mapper.")
+        if not sidecar:
+            return None
+        for column_data in sidecar.column_data:
+            self._add_column_data(column_data)
+
+        self._sidecar = sidecar
 
     def set_column_prefix_dict(self, column_prefix_dictionary, finalize_mapping=True):
         """ Replace the column prefix dictionary
@@ -123,12 +128,27 @@ def set_tag_columns(self, tag_columns=None, optional_tag_columns=None, finalize_
             return issues
         return []
 
+    def set_requested_columns(self, requested_columns, finalize_mapping=True):
+        """ Set to return only the columns listed in requested_columns
+
+        Args:
+            requested_columns(list or None): If this is not None, return ONLY these columns.  Names or numbers allowed.
+            finalize_mapping(bool): Finalize the mapping right now if True
+
+        Returns:
+        issues(list): An empty list of mapping issues
+        """
+        self._requested_columns = requested_columns
+        if finalize_mapping:
+            return self._finalize_mapping()
+        return []
+
     def set_column_map(self, new_column_map=None):
         """ Set the column number to name mapping.
 
         Args:
             new_column_map (list or dict):  Either an ordered list of the column names or column_number:column name
-                dictionary. In both cases column numbers start at 0
+                dictionary. In both cases, column numbers start at 0
 
         Returns:
             list: List of issues. Each issue is a dictionary.
@@ -172,7 +192,7 @@ def _expand_column(self, column_number, input_text):
         """
 
         # Default 1-1 mapping if we don't have specific behavior.
-        if not self._final_column_map:
+        if self._no_mapping_info:
             return HedString(input_text), False
 
         # If no entry, ignore this column.
@@ -298,31 +318,37 @@ def _finalize_mapping(self):
         self._final_column_map = {}
         found_named_tag_columns = {}
         all_tag_columns = self._tag_columns + self._optional_tag_columns
+        if self._requested_columns:
+            all_tag_columns += self._requested_columns
         self._finalize_mapping_issues = []
         if self._column_map is not None:
             for column_number, column_name in self._column_map.items():
-                if column_name in self.column_data:
+                name_requested = self._column_name_requested(column_name)
+                if name_requested and column_name in self.column_data:
                     column_entry = self.column_data[column_name]
                     column_entry.column_name = column_name
                     self._final_column_map[column_number] = column_entry
-                elif column_name in all_tag_columns:
+                elif name_requested and column_name in all_tag_columns:
                     found_named_tag_columns[column_name] = column_number
                 elif column_name.startswith(PANDAS_COLUMN_PREFIX_TO_IGNORE):
                     continue
-                elif self._has_sidecars:
+                elif self._sidecar:
                     if column_number not in all_tag_columns:
                         self._finalize_mapping_issues += ErrorHandler.format_error(ValidationErrors.HED_UNKNOWN_COLUMN,
                                                                                    extra_column_name=column_name)
 
         # Add any numbered columns
         for column_name, column_entry in self.column_data.items():
-            if isinstance(column_name, int):
+            if isinstance(column_name, int) and self._column_name_requested(column_name):
                 # Convert to internal numbering format
                 column_number = column_name
                 self._final_column_map[column_number] = column_entry
 
         # Add any tag columns
         for column_number in all_tag_columns:
+            name_requested = self._column_name_requested(column_number)
+            if not name_requested:
+                continue
             if isinstance(column_number, int):
                 if column_number not in self._final_column_map:
                     self._final_column_map[column_number] = ColumnMetadata(ColumnType.HEDTags, column_number)
@@ -338,8 +364,14 @@ def _finalize_mapping(self):
         for column_number, prefix in self._column_prefix_dictionary.items():
             self._set_column_prefix(column_number, prefix)
 
+        self._no_mapping_info = self._requested_columns is None and not self._final_column_map
         return self._finalize_mapping_issues
 
+    def _column_name_requested(self, column_name):
+        if self._requested_columns is None:
+            return True
+        return column_name in self._requested_columns
+
     def get_def_dicts(self):
         """ Return def dicts from every column description.
 
@@ -347,8 +379,9 @@ def get_def_dicts(self):
            list:   A list of DefinitionDict objects corresponding to each column entry.
 
         """
-        def_dicts = [entry.def_dict for entry in self.column_data.values()]
-        return def_dicts
+        if self._sidecar:
+            return self._sidecar.get_def_dicts()
+        return []
 
     def get_column_mapping_issues(self):
         """ Get all the issues with finalizing column mapping.  Primarily a missing required column.
@@ -358,24 +391,3 @@ def get_column_mapping_issues(self):
 
         """
         return self._finalize_mapping_issues
-
-    def validate_column_data(self, hed_ops, error_handler=None, **kwargs):
-        """ Validate the column data.
-
-        Args:
-            hed_ops (list, func, or HedOps): A func, a HedOps or a list of these to apply to the
-                hed strings in the sidecars.
-            error_handler (ErrorHandler or None): Used to report errors.  Uses a default one if none passed in.
-            kwargs: See models.hed_ops.translate_ops or the specific hed_ops for additional options.
-
-        Returns:
-            list: A list of syntax and semantic issues found in the definitions. Each issue is a dictionary.
-
-        """
-        if error_handler is None:
-            error_handler = ErrorHandler()
-        all_validation_issues = []
-        for column_data in self.column_data.values():
-            all_validation_issues += column_data.validate_column(hed_ops, error_handler=error_handler, **kwargs)
-
-        return all_validation_issues
diff --git a/hed/models/column_metadata.py b/hed/models/column_metadata.py
index 80e5604bf..2eab23516 100644
--- a/hed/models/column_metadata.py
+++ b/hed/models/column_metadata.py
@@ -1,17 +1,14 @@
 from enum import Enum
 from hed.models.hed_string import HedString
-from hed.models.definition_dict import DefinitionDict
-from hed.errors.error_types import SidecarErrors, ErrorContext, ValidationErrors
-from hed.errors import error_reporter
+from hed.errors.error_types import SidecarErrors, ValidationErrors
 from hed.errors.error_reporter import ErrorHandler
-from hed.models.hed_ops import translate_ops
-import copy
 
 
 class ColumnType(Enum):
-    """The overall column_type of a column in column mapper, eg treat it as HED tags.
+    """ The overall column_type of a column in column mapper, eg treat it as HED tags.
 
-    Mostly internal to column mapper related code"""
+        Mostly internal to column mapper related code
+    """
     Unknown = None
     # Do not return this column at all
     Ignore = "ignore"
@@ -26,28 +23,25 @@ class ColumnType(Enum):
 
 
 class ColumnMetadata:
-    """ Column in a ColumnMapper or top-level Sidecar dict. """
+    """ Column in a ColumnMapper. """
 
-    def __init__(self, column_type=None, name=None, hed_dict=None, column_prefix=None, error_handler=None):
+    def __init__(self, column_type=None, name=None, hed_dict=None, column_prefix=None):
         """ A single column entry in the column mapper.
 
         Args:
             column_type (ColumnType or None): How to treat this column when reading data.
             name (str, int, or None): The column_name or column number identifying this column.
                 If name is a string, you'll need to use a column map to set the number later.
-            hed_dict (dict or None): The loaded data (usually from json) for the given def
-                At a minimum, this needs "HED" in the dict for several ColumnType
+            hed_dict (dict or str or None): The loaded data (usually from json) for the given def
+                                     For category columns, this is a dict.
+                                     For value columns, it's a string.
             column_prefix (str or None): If present, prepend the given column_prefix to all hed tags in the columns.
                 Only works on ColumnType HedTags.
-            error_handler (ErrorHandler or None): Used to report errors.  Uses a default if None.
 
         Notes:
             - Each column from which data is retrieved must have a ColumnMetadata representing its contents.
             - The column_prefix dictionaries are used when the column is processed.
         """
-        if column_type is None or column_type == ColumnType.Unknown:
-            column_type = ColumnMetadata._detect_column_type(hed_dict)
-
         if hed_dict is None:
             hed_dict = {}
 
@@ -55,129 +49,17 @@ def __init__(self, column_type=None, name=None, hed_dict=None, column_prefix=Non
         self.column_name = name
         self.column_prefix = column_prefix
         self._hed_dict = hed_dict
-        self._def_removed_hed_dict = {}
-        self._def_dict = self.extract_definitions(error_handler=error_handler)
-
-    @property
-    def def_dict(self):
-        """ Return the definition dictionary for this column.
-
-        Returns:
-            DefinitionDict: Contains all the definitions located in the column.
-
-        """
-        return self._def_dict
 
     @property
     def hed_dict(self):
-        """ The loaded dict for any given entry.
+        """ The hed strings for any given entry.
 
         Returns:
-            dict: A dict which generally contains a "HED" entry and optional others like description.
+            dict or str: A string or dict of strings for this column
 
         """
         return self._hed_dict
 
-    def hed_string_iter(self, hed_ops=None, error_handler=None, **kwargs):
-        """ Iterator yielding column hed strings.
-
-        Args:
-            hed_ops (func, HedOps, or list of these): The HedOps or funcs to apply to the hed strings before returning.
-            error_handler (ErrorHandler): The error handler to use for context, uses a default one if none.
-            kwargs: See models.hed_ops.translate_ops or the specific hed_ops for additional options
-
-        Yields:
-            tuple:
-                - HedString: The hed string at a given column and key position.
-                - str: Indication of the where hed string was loaded from so it can be later set by the user.
-                - list: Issues found applying hed_ops. Each issue is a dictionary.
-
-        """
-        if error_handler is None:
-            error_handler = ErrorHandler()
-
-        if not isinstance(self._hed_dict, dict):
-            return
-
-        tag_funcs = []
-        if hed_ops:
-            tag_funcs = translate_ops(hed_ops, error_handler=error_handler, **kwargs)
-
-        for hed_string_obj, key_name in self._hed_iter():
-            new_col_issues = []
-            error_handler.push_error_context(ErrorContext.SIDECAR_KEY_NAME, key_name)
-            if not hed_string_obj:
-                new_col_issues += ErrorHandler.format_error(SidecarErrors.BLANK_HED_STRING)
-                error_handler.add_context_to_issues(new_col_issues)
-                yield hed_string_obj, key_name, new_col_issues
-            else:
-                error_handler.push_error_context(ErrorContext.HED_STRING, hed_string_obj,
-                                                 increment_depth_after=False)
-                if tag_funcs:
-                    new_col_issues += hed_string_obj.apply_funcs(tag_funcs)
-
-                error_handler.add_context_to_issues(new_col_issues)
-                yield hed_string_obj, key_name, new_col_issues
-                error_handler.pop_error_context()
-            error_handler.pop_error_context()
-
-    def _hed_iter(self, also_return_bad_types=False):
-        """ Iterate over the hed string entries.
-
-        Args:
-            also_return_bad_types (bool): If true, this can yield types other than HedString, otherwise skips these.
-
-        Yields:
-            tuple:
-                - HedString: Individual hed strings for different entries.
-                - str: The position to pass back to set this string.
-
-        """
-        hed_strings = self._hed_dict.get("HED", None)
-        if isinstance(hed_strings, dict):
-            for key, hed_string in hed_strings.items():
-                if isinstance(hed_string, str):
-                    hed_string = HedString(hed_string)
-                elif not also_return_bad_types:
-                    continue
-
-                yield hed_string, key
-        elif isinstance(hed_strings, str):
-            hed_string = HedString(hed_strings)
-            yield hed_string, None
-
-    def set_hed_string(self, new_hed_string, position=None, set_def_removed=False):
-        """ Set a hed string for a category key/etc.
-
-        Args:
-            new_hed_string (str or HedString): The new hed_string to replace the value at position.
-            position (str, optional): This should only be a value returned from hed_string_iter.
-            set_def_removed (bool): If True, set the version with definitions removed, rather than the normal version.
-
-        Raises:
-            TypeError: If the mapping cannot occur.
-
-        """
-        hed_strings = self._hed_dict.get("HED", None)
-        if isinstance(hed_strings, dict):
-            if position is None:
-                raise TypeError("Error: Trying to set a category HED string with no category")
-            if position not in self._hed_dict["HED"]:
-                raise TypeError("Error: Not allowed to add new categories to a column")
-            if set_def_removed:
-                self._def_removed_hed_dict[position] = str(new_hed_string)
-            else:
-                self._hed_dict["HED"][position] = str(new_hed_string)
-        elif isinstance(hed_strings, (str, HedString)):
-            if position is not None:
-                raise TypeError("Error: Trying to set a value HED string with a category")
-            if set_def_removed:
-                self._def_removed_hed_dict = str(new_hed_string)
-            else:
-                self._hed_dict["HED"] = str(new_hed_string)
-        else:
-            raise TypeError("Error: Trying to set a HED string on a column_type that doesn't support it.")
-
     def _get_category_hed_string(self, category):
         """ Fetch the hed string for a category key.
 
@@ -191,7 +73,7 @@ def _get_category_hed_string(self, category):
         if self.column_type != ColumnType.Categorical:
             return None
 
-        return self._def_removed_hed_dict.get(category, None)
+        return self._hed_dict.get(category, None)
 
     def _get_value_hed_string(self):
         """ Fetch the hed string in a value column.
@@ -203,7 +85,7 @@ def _get_value_hed_string(self):
         if self.column_type != ColumnType.Value:
             return None
 
-        return self._def_removed_hed_dict
+        return self._hed_dict
 
     def expand(self, input_text):
         """ Expand text using the rules for this column.
@@ -228,7 +110,7 @@ def expand(self, input_text):
                 return HedString(final_text), False
             else:
                 return None, ErrorHandler.format_error(ValidationErrors.HED_SIDECAR_KEY_MISSING, invalid_key=input_text,
-                                                       category_keys=list(self._hed_dict["HED"].keys()))
+                                                       category_keys=list(self._hed_dict.keys()))
         elif column_type == ColumnType.Value:
             prelim_text = self._get_value_hed_string()
             final_text = prelim_text.replace("#", input_text)
@@ -280,168 +162,23 @@ def remove_prefix(self, original_tag, current_tag_text):
         return current_tag_text
 
     @staticmethod
-    def _detect_column_type(dict_for_entry):
-        """ Determine the ColumnType of a given json entry.
-
-        Args:
-            dict_for_entry (dict): The loaded json entry a specific column.
-                Generally has a "HED" entry among other optional ones.
-
-        Returns:
-            ColumnType: The determined type of given column.  Returns None if unknown.
-
-        """
-        if not dict_for_entry or not isinstance(dict_for_entry, dict):
-            return ColumnType.Attribute
-
-        minimum_required_keys = ("HED",)
-        if not set(minimum_required_keys).issubset(dict_for_entry.keys()):
-            return ColumnType.Attribute
-
-        hed_entry = dict_for_entry["HED"]
-        if isinstance(hed_entry, dict):
-            return ColumnType.Categorical
-
-        if not isinstance(hed_entry, str):
-            return None
-
-        if "#" not in dict_for_entry["HED"]:
-            return None
-
-        return ColumnType.Value
-
-    def get_definition_issues(self):
-        """ Return the issues found extracting definitions.
-
-        Returns:
-            list: A list of issues found when parsing definitions. Individual issues are dictionaries.
-
-        """
-        return self._def_dict.get_definition_issues()
-
-    def validate_column(self, hed_ops, error_handler, **kwargs):
-        """ Run the given hed_ops on this column.
-
-        Args:
-            hed_ops (list or func or HedOps) A list of HedOps of funcs or a HedOps or func to apply
-                to the hed strings in the columns.
-            error_handler (ErrorHandler or None): Used to report errors.  Uses a default one if none passed in.
-            kwargs: See models.hed_ops.translate_ops or the specific hed_ops for additional options.
-
-        Returns:
-            list: Issues found by the given hed_ops. Each issue is a dictionary.
-
-        """
-        if error_handler is None:
-            error_handler = error_reporter.ErrorHandler()
-
-        if not isinstance(hed_ops, list):
-            hed_ops = [hed_ops]
-        hed_ops = hed_ops.copy()
-        hed_ops.append(self._validate_pound_sign_count)
-        error_handler.push_error_context(ErrorContext.SIDECAR_COLUMN_NAME, self.column_name)
-
-        col_validation_issues = self._run_ops(hed_ops, allow_placeholders=True,
-                                              error_handler=error_handler, **kwargs)
-        col_validation_issues += self._validate_column_structure(error_handler)
-        col_validation_issues += self.get_definition_issues()
-        error_handler.pop_error_context()
-        return col_validation_issues
-
-    def _validate_column_structure(self, error_handler):
-        """ Checks primarily for type errors such as expecting a string and getting a list in a json sidecar.
+    def expected_pound_sign_count(column_type):
+        """ Return how many pound signs a column string should have.
 
         Args:
-            error_handler (ErrorHandler)  Sets the context for the error reporting. Cannot be None.
+            column_type(ColumnType): The type of the column
 
         Returns:
-            list:  Issues in performing the operations. Each issue is a dictionary.
-
-        """
-        val_issues = []
-        if self.column_type is None:
-            val_issues += ErrorHandler.format_error(SidecarErrors.UNKNOWN_COLUMN_TYPE,
-                                                    column_name=self.column_name)
-        elif self.column_type == ColumnType.Categorical:
-            raw_hed_dict = self._hed_dict["HED"]
-            if not raw_hed_dict:
-                val_issues += ErrorHandler.format_error(SidecarErrors.BLANK_HED_STRING)
-
-        error_handler.add_context_to_issues(val_issues)
-
-        for hed_string_obj, key_name in self._hed_iter(also_return_bad_types=True):
-            new_col_issues = []
-            error_handler.push_error_context(ErrorContext.SIDECAR_KEY_NAME, key_name)
-            if not isinstance(hed_string_obj, HedString):
-                new_col_issues += ErrorHandler.format_error(SidecarErrors.WRONG_HED_DATA_TYPE,
-                                                            given_type=type(hed_string_obj),
-                                                            expected_type="str")
-                error_handler.add_context_to_issues(new_col_issues)
-            val_issues += new_col_issues
-
-        return val_issues
-
-    def _run_ops(self, hed_ops, error_handler, **kwargs):
-        col_validation_issues = []
-        for _, _, col_issues in self.hed_string_iter(hed_ops, error_handler=error_handler, **kwargs):
-            col_validation_issues += col_issues
-
-        return col_validation_issues
-
-    def _validate_pound_sign_count(self, hed_string):
-        """ Check if a given hed string in the column has the correct number of pound signs.
-
-        Args:
-            hed_string (str or HedString): HED string to be checked.
-
-        Returns:
-            list: Issues due to pound sign errors. Each issue is a dictionary.
-
-        Notes:
-            Normally the number of # should be either 0 or 1, but sometimes will be higher due to the
-            presence of definition tags.
-
+            tuple:
+                expected_count(int): The expected count.  0 or 1
+                error_type(str): The type of the error we should issue
         """
-        if self.column_type == ColumnType.Value or self.column_type == ColumnType.Attribute:
-            expected_pound_sign_count = 1
+        if column_type == ColumnType.Value or column_type == ColumnType.Attribute:
+            expected_count = 1
             error_type = SidecarErrors.INVALID_POUND_SIGNS_VALUE
-        elif self.column_type == ColumnType.HEDTags or self.column_type == ColumnType.Categorical:
-            expected_pound_sign_count = 0
+        elif column_type == ColumnType.HEDTags or column_type == ColumnType.Categorical:
+            expected_count = 0
             error_type = SidecarErrors.INVALID_POUND_SIGNS_CATEGORY
         else:
-            return []
-
-        # Make a copy without definitions to check placeholder count.
-        hed_string_copy = copy.deepcopy(hed_string)
-        hed_string_copy.remove_definitions()
-
-        if hed_string_copy.lower().count("#") != expected_pound_sign_count:
-            return ErrorHandler.format_error(error_type, pound_sign_count=str(hed_string_copy).count("#"))
-
-        return []
-
-    def extract_definitions(self, error_handler=None):
-        """ Gather and validate definitions in metadata.
-
-        Args:
-            error_handler (ErrorHandler): The error handler to use for context, uses a default one if None.
-
-        Returns:
-            DefinitionDict: Contains all the definitions located in the column.
-            issues: List of issues encountered in extracting the definitions. Each issue is a dictionary.
-
-        """
-        if error_handler is None:
-            error_handler = ErrorHandler()
-        new_def_dict = DefinitionDict()
-        hed_ops = []
-        hed_ops.append(new_def_dict)
-        hed_ops.append(HedString.remove_definitions)
-
-        all_issues = []
-        for hed_string, key_name, issues in self.hed_string_iter(hed_ops=hed_ops, allow_placeholders=True,
-                                                                 error_handler=error_handler):
-            self.set_hed_string(hed_string, key_name, set_def_removed=True)
-            all_issues += issues
-
-        return new_def_dict
+            return 0, None
+        return expected_count, error_type
diff --git a/hed/models/hed_ops.py b/hed/models/hed_ops.py
index 07cc87f8d..68f5bbdc7 100644
--- a/hed/models/hed_ops.py
+++ b/hed/models/hed_ops.py
@@ -2,6 +2,8 @@
 
 from functools import partial
 from hed.schema import HedSchema, HedSchemaGroup
+from hed.errors.error_types import ErrorContext, SidecarErrors
+from hed.errors import ErrorHandler
 
 
 # These are the defaults if you pass in nothing.  Most built in routes will have other default values.
@@ -22,8 +24,7 @@ def translate_ops(hed_ops, split_ops=False, **kwargs):
     Args:
         hed_ops (list): A list of func or HedOps or HedSchema to apply to hed strings.
         split_ops (bool): If true, will split the operations into separate lists of tag and string operations.
-
-    kwargs (dict):  An optional dictionary of name-value pairs representing parameters passed to each HedOps
+        kwargs (dict):  An optional dictionary of name-value pairs representing parameters passed to each HedOps
 
     Returns:
         list or tuple: A list of functions to apply or a tuple containing separate lists of tag and string ops.
@@ -74,6 +75,143 @@ def translate_ops(hed_ops, split_ops=False, **kwargs):
     return tag_funcs + string_funcs
 
 
+def apply_ops(hed_strings, hed_ops, **kwargs):
+    """ Convenience function to update a list/dict of hed strings
+
+    Args:
+        hed_strings(str, dict, list): A list/dict/str to update
+        hed_ops (list or HedOps or func): A list of func or HedOps or HedSchema to apply to hed strings.
+        kwargs (dict):  An optional dictionary of name-value pairs representing parameters passed to each HedOps
+
+    Returns:
+        tuple:
+            hed_strings(str, dict, list): Same type as input
+            issues(list): A list of issues found applying the hed_ops
+    """
+    from hed.models.hed_string import HedString
+
+    if not hed_strings:
+        return hed_strings, []
+    issues = []
+    tag_funcs = translate_ops(hed_ops, **kwargs)
+    if isinstance(hed_strings, str):
+        hed_string_obj = HedString(hed_strings)
+        issues += hed_string_obj.apply_funcs(tag_funcs)
+        return str(hed_string_obj), issues
+    elif isinstance(hed_strings, dict):
+        return_dict = {}
+        for key, hed_string in hed_strings.items():
+            hed_string_obj = HedString(hed_string)
+            issues += hed_string_obj.apply_funcs(tag_funcs)
+            return_dict[key] = str(hed_string_obj)
+        return return_dict, issues
+    elif isinstance(hed_strings, list):
+        return_list = []
+        for hed_string in hed_strings:
+            hed_string_obj = HedString(hed_string)
+            issues += hed_string_obj.apply_funcs(tag_funcs)
+            return_list.append(str(hed_string_obj))
+        return return_list, issues
+
+    raise ValueError("Unaccounted for type in apply_ops")
+
+
+def hed_string_iter(hed_strings, tag_funcs, error_handler):
+    """ Iterate over the given dict of strings, returning HedStrings
+
+        Also gives issues for blank strings
+
+    Args:
+        hed_strings(dict or str): A hed_string or dict of hed strings
+        tag_funcs (list of funcs): The functions to apply before returning
+        error_handler (ErrorHandler): The error handler to use for context, uses a default one if none.
+
+    Yields:
+        tuple:
+            - HedString: The hed string at a given column and key position.
+            - str: Indication of the where hed string was loaded from so it can be later set by the user.
+            - list: Issues found applying hed_ops. Each issue is a dictionary.
+
+    """
+    for hed_string_obj, key_name in _hed_iter_low(hed_strings):
+        new_col_issues = []
+        error_handler.push_error_context(ErrorContext.SIDECAR_KEY_NAME, key_name)
+        if not hed_string_obj:
+            new_col_issues += ErrorHandler.format_error(SidecarErrors.BLANK_HED_STRING)
+            error_handler.add_context_to_issues(new_col_issues)
+            yield hed_string_obj, key_name, new_col_issues
+        else:
+            error_handler.push_error_context(ErrorContext.HED_STRING, hed_string_obj,
+                                             increment_depth_after=False)
+            if tag_funcs:
+                new_col_issues += hed_string_obj.apply_funcs(tag_funcs)
+
+            error_handler.add_context_to_issues(new_col_issues)
+            yield hed_string_obj, key_name, new_col_issues
+            error_handler.pop_error_context()
+        error_handler.pop_error_context()
+
+
+def _hed_iter_low(hed_strings):
+    """ Iterate over the hed string entries.
+
+        Used by hed_string_iter
+
+    Args:
+        hed_strings(dict or str): A hed_string or dict of hed strings
+
+    Yields:
+        tuple:
+            - HedString: Individual hed strings for different entries.
+            - str: The position to pass back to set this string.
+
+    """
+    from hed.models.hed_string import HedString
+
+    if isinstance(hed_strings, dict):
+        for key, hed_string in hed_strings.items():
+            if isinstance(hed_string, str):
+                hed_string = HedString(hed_string)
+            else:
+                continue
+            yield hed_string, key
+    elif isinstance(hed_strings, str):
+        hed_string = HedString(hed_strings)
+        yield hed_string, None
+
+
+def set_hed_string(new_hed_string, hed_strings, position=None):
+    """ Set a hed string for a category key/etc.
+
+    Args:
+        new_hed_string (str or HedString): The new hed_string to replace the value at position.
+        hed_strings(dict or str or HedString): The hed strings we want to update
+        position (str, optional): This should only be a value returned from hed_string_iter.
+
+    Returns:
+        updated_string (str or dict): The newly updated string/dict.
+    Raises:
+        TypeError: If the mapping cannot occur.
+
+    """
+    from hed.models.hed_string import HedString
+
+    if isinstance(hed_strings, dict):
+        if position is None:
+            raise TypeError("Error: Trying to set a category HED string with no category")
+        if position not in hed_strings:
+            raise TypeError("Error: Not allowed to add new categories to a column")
+        hed_strings[position] = str(new_hed_string)
+    elif isinstance(hed_strings, (str, HedString)):
+        if position is not None:
+            raise TypeError("Error: Trying to set a value HED string with a category")
+        hed_strings = str(new_hed_string)
+    else:
+        raise TypeError("Error: Trying to set a HED string on a column_type that doesn't support it.")
+
+    return hed_strings
+
+
 class HedOps:
     """ Base class to support HedOps.
 
diff --git a/hed/models/sidecar.py b/hed/models/sidecar.py
index 18c9fa0a5..2db19e034 100644
--- a/hed/models/sidecar.py
+++ b/hed/models/sidecar.py
@@ -1,42 +1,88 @@
 import json
 from hed.models.column_metadata import ColumnMetadata
-from hed.errors.error_types import ErrorContext
-from hed.errors import error_reporter
+from hed.errors.error_types import ErrorContext, SidecarErrors
 from hed.errors import ErrorHandler
 from hed.errors.exceptions import HedFileError, HedExceptions
 from hed.models.hed_string import HedString
-from hed.models.def_mapper import DefMapper
+from hed.models.column_metadata import ColumnType
+from hed.models.hed_ops import apply_ops, hed_string_iter, set_hed_string
+from hed.models.sidecar_base import SidecarBase
 
 
-class Sidecar:
+class Sidecar(SidecarBase):
     """ Contents of a JSON file or merged file.
 
-    Notes:
-        - The Sidecar maintains its own definition dictionaries.
-
     """
 
-    def __init__(self, file, name=None):
+    def __init__(self, files, name=None):
         """ Construct a Sidecar object representing a JSON file.
 
         Args:
-            file (str or FileLike): A string or file-like object representing a JSON file.
+            files (str or FileLike or list): A string or file-like object representing a JSON file, or a list of such.
             name (str or None): Optional name identifying this sidecar, generally a filename.
 
         """
-        self._column_data = {}
-        self.name = name
-        if file:
-            self.load_sidecar_file(file)
+        super().__init__(name)
+        self.loaded_dict = self.load_sidecar_files(files)
+        self.def_dict = self.extract_definitions()
 
-    def __iter__(self):
-        """ An iterator to go over the individual column metadata.
+    @property
+    def column_data(self):
+        """ Generates the list of ColumnMetadata for this sidecar
 
         Returns:
-            iterator: An iterator over the column metadata values.
+            list(ColumnMetadata): the list of column metadata defined by this sidecar
+        """
+        for col_name, col_dict in self.loaded_dict.items():
+            yield self._generate_single_column(col_name, col_dict)
+
+    def _hed_string_iter(self, tag_funcs, error_handler):
+        """ Low level function to retrieve hed string in sidecar
+
+        Args:
+            tag_funcs(list): A list of functions to apply to returned strings
+            error_handler(ErrorHandler): Error handler to use for context
+
+        Yields:
+            tuple:
+                string(HedString): The retrieved and modified string
+                position(tuple): The location of this hed string.  Black box.
+                issues(list): A list of issues running the tag_funcs.
+        """
+        for column_name, dict_for_entry in self.loaded_dict.items():
+            error_handler.push_error_context(ErrorContext.SIDECAR_COLUMN_NAME, column_name)
+            hed_dict = dict_for_entry.get("HED", {})
+            for (hed_string_obj, position, issues) in hed_string_iter(hed_dict, tag_funcs, error_handler):
+                yield hed_string_obj, (column_name, position), issues
+
+            error_handler.pop_error_context()
+
+    def _set_hed_string(self, new_hed_string, position):
+        """ Low level function to update hed string in sidecar
+
+        Args:
+            new_hed_string (str or HedString): The new hed_string to replace the value at position.
+            position (tuple):   The value returned from hed_string_iter.
+        """
+        column_name, position = position
+        hed_dict = self.loaded_dict[column_name]
+        hed_dict["HED"] = set_hed_string(new_hed_string, hed_dict["HED"], position)
+
+    def validate_structure(self, error_handler):
+        """ Validate the raw structure of this sidecar.
+
+        Args:
+            error_handler(ErrorHandler): The error handler to use for error context
 
+        Returns:
+            issues(list): A list of issues found with the structure
         """
-        return iter(self._column_data.values())
+        all_validation_issues = []
+        for column_name, dict_for_entry in self.loaded_dict.items():
+            error_handler.push_error_context(ErrorContext.SIDECAR_COLUMN_NAME, column_name)
+            all_validation_issues += self._validate_column_structure(column_name, dict_for_entry, error_handler)
+            error_handler.pop_error_context()
+        return all_validation_issues
 
     def save_as_json(self, save_filename):
         """ Save column metadata to a JSON file.
@@ -45,11 +91,8 @@ def save_as_json(self, save_filename):
             save_filename (str): Path to save file
 
         """
-        output_dict = {}
-        for entry in self._column_data.values():
-            output_dict[entry.column_name] = entry.hed_dict
         with open(save_filename, "w") as fp:
-            json.dump(output_dict, fp, indent=4)
+            json.dump(self.loaded_dict, fp, indent=4)
 
     def get_as_json_string(self):
         """ Return this sidecar's column metadata as a string.
@@ -58,10 +101,7 @@ def get_as_json_string(self):
             str: The json string representing this sidecar.
 
         """
-        output_dict = {}
-        for entry in self._column_data.values():
-            output_dict[entry.column_name] = entry.hed_dict
-        return json.dumps(output_dict, indent=4)
+        return json.dumps(self.loaded_dict, indent=4)
 
     def load_sidecar_file(self, file):
         """ Load column metadata from a given json file.
@@ -71,140 +111,56 @@ def load_sidecar_file(self, file):
 
         Raises:
             HedFileError: If the file was not found or could not be parsed into JSON.
-
-        Notes:
-            - Multiple files can be loaded into one Sidecar, but it is discouraged.
-
         """
-        if isinstance(file, str):
+        if not file:
+            return {}
+        elif isinstance(file, str):
             try:
                 with open(file, "r") as fp:
                     if not self.name:
                         self.name = file
-                    self._load_json_columns(fp)
+                    return self._load_json_file(fp)
             except FileNotFoundError as e:
                 raise HedFileError(HedExceptions.FILE_NOT_FOUND, e.strerror, file)
             except TypeError as e:
                 raise HedFileError(HedExceptions.FILE_NOT_FOUND, str(e), file)
         else:
-            self._load_json_columns(file)
-
-    def _load_json_columns(self, fp):
-        """ Parse a JSON file into columns and load in the column entry dictionary.
-
-        Args:
-            fp (File-like): The JSON source stream.
+            return self._load_json_file(file)
 
-        Raises:
-            HedFileError:  If the file cannot be parsed.
-
-        """
-        try:
-            loaded_defs = json.load(fp)
-            for col_name, col_dict in loaded_defs.items():
-                self._add_single_column(col_name, col_dict)
-        except json.decoder.JSONDecodeError as e:
-            raise HedFileError(HedExceptions.CANNOT_PARSE_JSON, str(e), self.name)
-
-    @staticmethod
-    def load_multiple_sidecars(input_list):
-        """ Utility for loading multiple json files.
+    def load_sidecar_files(self, files):
+        """ Load json from a given file or list
 
         Args:
-            input_list (list): A list of filenames or Sidecar files in any mix.
-
-        Returns:
-            list: A list sidecars.
-
+            files (str or FileLike or list): A string or file-like object representing a JSON file, or a list of such.
         Raises:
-            HedFileError: If any of the files are not found.
-
-        """
-        if not isinstance(input_list, list):
-            input_list = [input_list]
-
-        loaded_files = []
-        for json_file in input_list:
-            if isinstance(json_file, str):
-                json_file = Sidecar(json_file)
-            loaded_files.append(json_file)
-        return loaded_files
-
-    def hed_string_iter(self, hed_ops=None, error_handler=None, expand_defs=False, remove_definitions=False,
-                        allow_placeholders=True, extra_def_dicts=None, **kwargs):
-        """ Iterator over hed strings in columns.
-
-        Args:
-            hed_ops (func, HedOps, list):  A HedOps, funcs or list of these to apply to the hed strings
-                                            before returning
-            error_handler (ErrorHandler): The error handler to use for context, uses a default one if none.
-            expand_defs (bool): If True, expand all def tags located in the strings.
-            remove_definitions (bool): If True, remove all definitions found in the string.
-            allow_placeholders (bool): If False, placeholders will be marked as validation warnings.
-            extra_def_dicts (DefinitionDict, list, None): Extra dicts to add to the list.
-            kwargs: See models.hed_ops.translate_ops or the specific hed_ops for additional options.
-
-        Yields:
-            tuple:
-                - HedString: A HedString at a given column and key position.
-                - tuple: Indicates where hed_string was loaded from so it can be later set by the user
-                - list: A list of issues found performing ops. Each issue is a dictionary.
-
+            HedFileError: If the file was not found or could not be parsed into JSON.
         """
-        if error_handler is None:
-            error_handler = ErrorHandler()
-        hed_ops = self._standardize_ops(hed_ops)
-        if expand_defs or remove_definitions:
-            self._add_definition_mapper(hed_ops, extra_def_dicts)
-        for column_name, entry in self._column_data.items():
-            error_handler.push_error_context(ErrorContext.SIDECAR_COLUMN_NAME, column_name)
-            for (hed_string_obj, position, issues) in entry.hed_string_iter(hed_ops=hed_ops,
-                                                                            error_handler=error_handler,
-                                                                            expand_defs=expand_defs,
-                                                                            allow_placeholders=allow_placeholders,
-                                                                            remove_definitions=remove_definitions,
-                                                                            **kwargs):
-                yield hed_string_obj, (column_name, position), issues
-
-            error_handler.pop_error_context()
-
-    def set_hed_string(self, new_hed_string, position):
-        """ Set a provided column/category key/etc.
+        if not files:
+            return {}
+        if not isinstance(files, list):
+            files = [files]
 
-        Args:
-            new_hed_string (str or HedString): The new hed_string to replace the value at position.
-            position (tuple):   The (HedString, str, list) tuple returned from hed_string_iter.
-
-        """
-        column_name, position = position
-        entry = self._column_data[column_name]
-        entry.set_hed_string(new_hed_string, position)
+        merged_dict = {}
+        for file in files:
+            loaded_json = self.load_sidecar_file(file)
+            merged_dict.update(loaded_json)
+        return merged_dict
 
-    def _add_definition_mapper(self, hed_ops, extra_def_dicts=None):
-        """ Add a DefMapper if the hed_ops list doesn't have one.
+    def _load_json_file(self, fp):
+        """ Load the raw json of a given file
 
         Args:
-            hed_ops (list):  A list of HedOps
-            extra_def_dicts (list):  DefDicts from outside.
-
-        Returns:
-            DefMapper:  A shallow copy of the hed_ops list with a DefMapper added if there wasn't one.
+            fp (File-like): The JSON source stream.
 
+        Raises:
+            HedFileError:  If the file cannot be parsed.
         """
-        if not any(isinstance(hed_op, DefMapper) for hed_op in hed_ops):
-            def_dicts = self.get_def_dicts(extra_def_dicts)
-            def_mapper = DefMapper(def_dicts)
-            hed_ops.append(def_mapper)
-            return def_mapper
-        return None
-
-    @staticmethod
-    def _standardize_ops(hed_ops):
-        if not isinstance(hed_ops, list):
-            hed_ops = [hed_ops]
-        return hed_ops.copy()
+        try:
+            return json.load(fp)
+        except json.decoder.JSONDecodeError as e:
+            raise HedFileError(HedExceptions.CANNOT_PARSE_JSON, str(e), self.name)
 
-    def _add_single_column(self, column_name, dict_for_entry, column_type=None):
+    def _generate_single_column(self, column_name, dict_for_entry, column_type=None):
         """ Create a single column metadata entry and add to this sidecar.
 
         Args:
@@ -214,56 +170,77 @@ def _add_single_column(self, column_name, dict_for_entry, column_type=None):
                 This overrides auto-detection from the dict_for_entry.
 
         """
-        column_entry = ColumnMetadata(column_type, column_name, dict_for_entry)
-        self._column_data[column_name] = column_entry
+        if column_type is None:
+            column_type = self._detect_column_type(dict_for_entry)
+        if dict_for_entry:
+            hed_dict = dict_for_entry.get("HED")
+        else:
+            hed_dict = None
+        def_removed_dict, _ = apply_ops(hed_dict, HedString.remove_definitions)
+        column_entry = ColumnMetadata(column_type, column_name, def_removed_dict)
+        return column_entry
 
-    def get_def_dicts(self, extra_def_dicts=None):
-        """ Return DefinitionDicts for the columns in this sidecar.
+    @staticmethod
+    def _detect_column_type(dict_for_entry):
+        """ Determine the ColumnType of a given json entry.
 
         Args:
-            extra_def_dicts (list, DefinitionDict, or None): Extra dicts to add to the list.
+            dict_for_entry (dict): The loaded json entry a specific column.
+                Generally has a "HED" entry among other optional ones.
 
         Returns:
-            list: A list of definition dicts for each column plus any found in extra_def_dicts.
+            ColumnType: The determined type of given column.  Returns None if unknown.
 
         """
-        def_dicts = [column_entry.def_dict for column_entry in self]
-        if extra_def_dicts:
-            if not isinstance(extra_def_dicts, list):
-                extra_def_dicts = [extra_def_dicts]
-            def_dicts += extra_def_dicts
-        return def_dicts
+        if not dict_for_entry or not isinstance(dict_for_entry, dict):
+            return ColumnType.Attribute
+
+        minimum_required_keys = ("HED",)
+        if not set(minimum_required_keys).issubset(dict_for_entry.keys()):
+            return ColumnType.Attribute
+
+        hed_entry = dict_for_entry["HED"]
+        if isinstance(hed_entry, dict):
+            return ColumnType.Categorical
+
+        if not isinstance(hed_entry, str):
+            return None
+
+        if "#" not in dict_for_entry["HED"]:
+            return None
+
+        return ColumnType.Value
 
-    def validate_entries(self, hed_ops=None, name=None, extra_def_dicts=None,
-                         error_handler=None, **kwargs):
-        """ Run the given hed_ops on all columns in this sidecar.
+    def _validate_column_structure(self, column_name, dict_for_entry, error_handler):
+        """ Checks primarily for type errors such as expecting a string and getting a list in a json sidecar.
 
         Args:
-            hed_ops (list, func, or HedOps): A HedOps, func or list of these to apply to hed strings in this sidecar.
-            name (str): If present, will use this as the filename for context, rather than using the actual filename
-                Useful for temp filenames.
-            extra_def_dicts: (DefinitionDict, list, or None): If present use these in addition to sidecar's def dicts.
-            error_handler (ErrorHandler or None): Used to report errors.  Uses a default one if none passed in.
-            kwargs: See models.hed_ops.translate_ops or the specific hed_ops for additional options.
+            error_handler (ErrorHandler)  Sets the context for the error reporting. Cannot be None.
 
         Returns:
-            list: The list of validation issues found. Individual issues are in the form of a dict.
+            list:  Issues in performing the operations. Each issue is a dictionary.
 
         """
-        if error_handler is None:
-            error_handler = error_reporter.ErrorHandler()
-        if not name:
-            name = self.name
-        if name:
-            error_handler.push_error_context(ErrorContext.FILE_NAME, name, False)
-
-        hed_ops = self._standardize_ops(hed_ops)
-        def_mapper = self._add_definition_mapper(hed_ops, extra_def_dicts)
-
-        all_validation_issues = []
-        all_validation_issues += def_mapper.issues
-        for column_data in self:
-            all_validation_issues += column_data.validate_column(hed_ops, error_handler=error_handler, **kwargs)
-        if name:
-            error_handler.pop_error_context()
-        return all_validation_issues
+        val_issues = []
+        column_type = self._detect_column_type(dict_for_entry=dict_for_entry)
+        if column_type is None:
+            val_issues += ErrorHandler.format_error(SidecarErrors.UNKNOWN_COLUMN_TYPE,
+                                                    column_name=column_name)
+        elif column_type == ColumnType.Categorical:
+            raw_hed_dict = dict_for_entry["HED"]
+            if not raw_hed_dict:
+                val_issues += ErrorHandler.format_error(SidecarErrors.BLANK_HED_STRING)
+            if not isinstance(raw_hed_dict, dict):
+                val_issues += ErrorHandler.format_error(SidecarErrors.WRONG_HED_DATA_TYPE,
+                                                        given_type=type(raw_hed_dict),
+                                                        expected_type="dict")
+            for key_name, hed_string in raw_hed_dict.items():
+                if not isinstance(hed_string, str):
+                    error_handler.push_error_context(ErrorContext.SIDECAR_KEY_NAME, key_name)
+                    val_issues += ErrorHandler.format_error(SidecarErrors.WRONG_HED_DATA_TYPE,
+                                                            given_type=type(hed_string),
+                                                            expected_type="str")
+                    error_handler.pop_error_context()
+        error_handler.add_context_to_issues(val_issues)
+
+        return val_issues
diff --git a/hed/models/sidecar_base.py b/hed/models/sidecar_base.py
new file mode 100644
index 000000000..4fcade309
--- /dev/null
+++ b/hed/models/sidecar_base.py
@@ -0,0 +1,266 @@
+import copy
+from hed.models.column_metadata import ColumnMetadata
+from hed.errors.error_types import ErrorContext
+from hed.errors import error_reporter
+from hed.errors import ErrorHandler
+from hed.models.hed_string import HedString
+from hed.models.def_mapper import DefMapper
+from hed.models.hed_ops import translate_ops, apply_ops
+from hed.models.definition_dict import DefinitionDict
+from functools import partial
+
+
+class SidecarBase:
+    """ Baseclass for specialized spreadsheet sidecars
+
+        To subclass this class, you'll want to override at the minimum:
+        _hed_string_iter
+        _set_hed_string
+        validate_structure
+        column_data property <- This is the only truly mandatory one
+
+    """
+    def __init__(self, name=None):
+        """ Initialize a sidecar baseclass
+
+        Args:
+            name (str or None): Optional name identifying this sidecar, generally a filename.
+
+        """
+        self.name = name
+        # Expected to be called in subclass after data is loaded
+        # self.def_dict = self.extract_definitions()
+
+    @property
+    def column_data(self):
+        """ Generates the list of ColumnMetadata for this sidecar
+
+        Returns:
+            list(ColumnMetadata): the list of column metadata defined by this sidecar
+        """
+        return []
+
+    def _hed_string_iter(self, tag_funcs, error_handler):
+        """ Low level function to retrieve hed string in sidecar
+
+        Args:
+            tag_funcs(list): A list of functions to apply to returned strings
+            error_handler(ErrorHandler): Error handler to use for context
+
+        Yields:
+            tuple:
+                string(HedString): The retrieved and modified string
+                position(tuple): The location of this hed string.  Black box.
+                issues(list): A list of issues running the tag_funcs.
+        """
+        yield
+
+    def _set_hed_string(self, new_hed_string, position):
+        """ Low level function to update hed string in sidecar
+
+        Args:
+            new_hed_string (str or HedString): The new hed_string to replace the value at position.
+            position (tuple):   The value returned from hed_string_iter.
+        """
+        return
+
+    def validate_structure(self, error_handler):
+        """ Validate the raw structure of this sidecar.
+
+        Args:
+            error_handler(ErrorHandler): The error handler to use for error context
+
+        Returns:
+            issues(list): A list of issues found with the structure
+        """
+        return []
+
+    def __iter__(self):
+        """ An iterator to go over the individual column metadata.
+
+        Returns:
+            iterator: An iterator over the column metadata values.
+
+        """
+        return iter(self.column_data)
+
+    def hed_string_iter(self, hed_ops=None, error_handler=None, expand_defs=False, remove_definitions=False,
+                        allow_placeholders=True, extra_def_dicts=None, **kwargs):
+        """ Iterator over hed strings in columns.
+
+        Args:
+            hed_ops (func, HedOps, list):  A HedOps, funcs or list of these to apply to the hed strings
+                                            before returning
+            error_handler (ErrorHandler): The error handler to use for context, uses a default one if none.
+            expand_defs (bool): If True, expand all def tags located in the strings.
+            remove_definitions (bool): If True, remove all definitions found in the string.
+            allow_placeholders (bool): If False, placeholders will be marked as validation warnings.
+            extra_def_dicts (DefinitionDict, list, None): Extra dicts to add to the list.
+            kwargs: See models.hed_ops.translate_ops or the specific hed_ops for additional options.
+
+        Yields:
+            tuple:
+                - HedString: A HedString at a given column and key position.
+                - tuple: Indicates where hed_string was loaded from so it can be later set by the user
+                - list: A list of issues found performing ops. Each issue is a dictionary.
+
+        """
+        if error_handler is None:
+            error_handler = ErrorHandler()
+        hed_ops = self._standardize_ops(hed_ops)
+        if expand_defs or remove_definitions:
+            self._add_definition_mapper(hed_ops, extra_def_dicts)
+        tag_funcs = translate_ops(hed_ops, error_handler=error_handler, expand_defs=expand_defs,
+                                  allow_placeholders=allow_placeholders, remove_definitions=remove_definitions,
+                                  **kwargs)
+
+        return self._hed_string_iter(tag_funcs, error_handler)
+
+    def set_hed_string(self, new_hed_string, position):
+        """ Set a provided column/category key/etc.
+
+        Args:
+            new_hed_string (str or HedString): The new hed_string to replace the value at position.
+            position (tuple):   The (HedString, str, list) tuple returned from hed_string_iter.
+
+        """
+        return self._set_hed_string(new_hed_string, position)
+
+    def _add_definition_mapper(self, hed_ops, extra_def_dicts=None):
+        """ Add a DefMapper if the hed_ops list doesn't have one.
+
+        Args:
+            hed_ops (list):  A list of HedOps
+            extra_def_dicts (list):  DefDicts from outside.
+
+        Returns:
+            DefMapper:  A shallow copy of the hed_ops list with a DefMapper added if there wasn't one.
+
+        """
+        def_mapper_list = [hed_op for hed_op in hed_ops if isinstance(hed_op, DefMapper)]
+
+        if not def_mapper_list:
+            def_dicts = self.get_def_dicts(extra_def_dicts)
+            def_mapper = DefMapper(def_dicts)
+            hed_ops.append(def_mapper)
+            return def_mapper
+        return def_mapper_list[0]
+
+    @staticmethod
+    def _standardize_ops(hed_ops):
+        if not isinstance(hed_ops, list):
+            hed_ops = [hed_ops]
+        return hed_ops.copy()
+
+    def get_def_dicts(self, extra_def_dicts=None):
+        """ Returns the definition dict for this sidecar.
+
+        Args:
+            extra_def_dicts (list, DefinitionDict, or None): Extra dicts to add to the list.
+
+        Returns:
+            list: A list with the sidecar def_dict plus any found in extra_def_dicts.
+
+        """
+        def_dicts = [self.def_dict]
+        if extra_def_dicts:
+            if not isinstance(extra_def_dicts, list):
+                extra_def_dicts = [extra_def_dicts]
+            def_dicts += extra_def_dicts
+        return def_dicts
+
+    def validate_entries(self, hed_ops=None, name=None, extra_def_dicts=None,
+                         error_handler=None, **kwargs):
+        """ Run the given hed_ops on all columns in this sidecar.
+
+        Args:
+            hed_ops (list, func, or HedOps): A HedOps, func or list of these to apply to hed strings in this sidecar.
+            name (str): If present, will use this as the filename for context, rather than using the actual filename
+                Useful for temp filenames.
+            extra_def_dicts: (DefinitionDict, list, or None): If present use these in addition to sidecar's def dicts.
+            error_handler (ErrorHandler or None): Used to report errors.  Uses a default one if none passed in.
+            kwargs: See models.hed_ops.translate_ops or the specific hed_ops for additional options.
+
+        Returns:
+            list: The list of validation issues found. Individual issues are in the form of a dict.
+
+        """
+        if error_handler is None:
+            error_handler = error_reporter.ErrorHandler()
+        if not name:
+            name = self.name
+        if name:
+            error_handler.push_error_context(ErrorContext.FILE_NAME, name, False)
+
+        all_validation_issues = self.validate_structure(error_handler)
+
+        # Early out major errors so the rest of our code can assume they won't happen.
+        if all_validation_issues:
+            return all_validation_issues
+
+        hed_ops = self._standardize_ops(hed_ops)
+        def_mapper = self._add_definition_mapper(hed_ops, extra_def_dicts)
+        all_validation_issues += def_mapper.issues
+
+        for hed_string, key_name, issues in self.hed_string_iter(hed_ops=hed_ops, allow_placeholders=True,
+                                                                 error_handler=error_handler, **kwargs):
+            self.set_hed_string(hed_string, key_name)
+            all_validation_issues += issues
+
+        # Finally check what requires the final mapped data to check
+        for column_data in self.column_data:
+            validate_pound_func = partial(self._validate_pound_sign_count, column_type=column_data.column_type)
+            _, issues = apply_ops(column_data.hed_dict, validate_pound_func)
+            all_validation_issues += issues
+        all_validation_issues += self.def_dict.get_definition_issues()
+        if name:
+            error_handler.pop_error_context()
+        return all_validation_issues
+
+    def extract_definitions(self, error_handler=None):
+        """ Gather and validate definitions in metadata.
+
+        Args:
+            error_handler (ErrorHandler): The error handler to use for context, uses a default one if None.
+
+        Returns:
+            DefinitionDict: Contains all the definitions located in the column.
+            issues: List of issues encountered in extracting the definitions. Each issue is a dictionary.
+
+        """
+        if error_handler is None:
+            error_handler = ErrorHandler()
+        new_def_dict = DefinitionDict()
+        hed_ops = []
+        hed_ops.append(new_def_dict)
+
+        all_issues = []
+        for hed_string, key_name, issues in self.hed_string_iter(hed_ops=hed_ops, allow_placeholders=True,
+                                                                 error_handler=error_handler):
+            all_issues += issues
+
+        return new_def_dict
+
+    def _validate_pound_sign_count(self, hed_string, column_type):
+        """ Check if a given hed string in the column has the correct number of pound signs.
+
+        Args:
+            hed_string (str or HedString): HED string to be checked.
+
+        Returns:
+            list: Issues due to pound sign errors. Each issue is a dictionary.
+
+        Notes:
+            Normally the number of # should be either 0 or 1, but sometimes will be higher due to the
+            presence of definition tags.
+
+        """
+        # Make a copy without definitions to check placeholder count.
+        expected_count, error_type = ColumnMetadata.expected_pound_sign_count(column_type)
+        hed_string_copy = copy.deepcopy(hed_string)
+        hed_string_copy.remove_definitions()
+
+        if hed_string_copy.lower().count("#") != expected_count:
+            return ErrorHandler.format_error(error_type, pound_sign_count=str(hed_string_copy).count("#"))
+
+        return []
diff --git a/hed/models/tabular_input.py b/hed/models/tabular_input.py
index 9825c304b..42076ccbc 100644
--- a/hed/models/tabular_input.py
+++ b/hed/models/tabular_input.py
@@ -29,19 +29,19 @@ def __init__(self, file=None, sidecar=None, attribute_columns=None, extra_def_di
         """
         if attribute_columns is None:
             attribute_columns = ["duration", "onset"]
-        if sidecar:
-            sidecar_list = Sidecar.load_multiple_sidecars(sidecar)
-        else:
-            sidecar_list = None
-        new_mapper = ColumnMapper(sidecars=sidecar_list, optional_tag_columns=[self.HED_COLUMN_NAME],
+        if sidecar and not isinstance(sidecar, Sidecar):
+            sidecar = Sidecar(sidecar)
+        new_mapper = ColumnMapper(sidecar=sidecar, optional_tag_columns=[self.HED_COLUMN_NAME],
                                   attribute_columns=attribute_columns)
 
+        definition_columns = [self.HED_COLUMN_NAME]
+        self._sidecar = sidecar
         self._also_gather_defs = also_gather_defs
         self._extra_def_dicts = extra_def_dicts
         def_mapper = self.create_def_mapper(new_mapper, extra_def_dicts)
 
         super().__init__(file, file_type=".tsv", worksheet_name=None, has_column_names=True, mapper=new_mapper,
-                         def_mapper=def_mapper, name=name)
+                         def_mapper=def_mapper, name=name, definition_columns=definition_columns)
 
         if not self._has_column_names:
             raise ValueError("You are attempting to open a bids_old style file with no column headers provided.\n"
@@ -73,16 +73,16 @@ def mapper (DefMapper): A class to validate or expand definitions with the given
 
         return def_mapper
 
-    def reset_column_mapper(self, sidecars=None, attribute_columns=None):
+    def reset_column_mapper(self, sidecar=None, attribute_columns=None):
         """ Change the sidecars and settings.
 
         Args:
-            sidecars (str or [str] or Sidecar or [Sidecar]): A list of json filenames to pull sidecar info from.
+            sidecar (str or [str] or Sidecar or [Sidecar]): A list of json filenames to pull sidecar info from.
             attribute_columns (str or int or [str] or [int]): Column names or numbers to treat as attributes.
                     Default: ["duration", "onset"]
 
         """
-        new_mapper = ColumnMapper(sidecars=sidecars, optional_tag_columns=[self.HED_COLUMN_NAME],
+        new_mapper = ColumnMapper(sidecar=sidecar, optional_tag_columns=[self.HED_COLUMN_NAME],
                                   attribute_columns=attribute_columns)
 
         self._def_mapper = self.create_def_mapper(new_mapper, self._extra_def_dicts)
@@ -106,4 +106,4 @@ def validate_file_sidecars(self, hed_ops=None, error_handler=None, **kwargs):
         if not isinstance(hed_ops, list):
             hed_ops = [hed_ops]
         hed_ops.append(self._def_mapper)
-        return self._mapper.validate_column_data(hed_ops, error_handler=error_handler, **kwargs)
+        return self._sidecar.validate_entries(hed_ops, error_handler=error_handler, **kwargs)
diff --git a/hed/schema/hed_cache.py b/hed/schema/hed_cache.py
index 05743e5b7..5a7cf1525 100644
--- a/hed/schema/hed_cache.py
+++ b/hed/schema/hed_cache.py
@@ -10,7 +10,7 @@
 from semantic_version import Version
 import portalocker
 import time
-from hed.util.file_util import url_to_file
+from hed.schema.schema_io.schema_util import url_to_file
 
 """Infrastructure for caching HED schema from remote repositories."""
 
@@ -128,13 +128,18 @@ def cache_specific_url(hed_xml_url, xml_version=None, library_name=None, cache_f
     filename = hed_xml_url.split('/')[-1]
     cache_filename = os.path.join(cache_folder, filename)
 
+    return _cache_specific_url(hed_xml_url, cache_filename)
+
+
+def _cache_specific_url(hed_xml_url, cache_filename):
+    cache_folder = cache_filename.rpartition("/")[0]
     os.makedirs(cache_folder, exist_ok=True)
     temp_hed_xml_file = url_to_file(hed_xml_url)
     if temp_hed_xml_file:
-        cache_filename = _safe_copy_tmp_to_folder(temp_hed_xml_file, cache_filename)
+        cache_filename = _safe_move_tmp_to_folder(temp_hed_xml_file, cache_filename)
+        os.remove(temp_hed_xml_file)
         return cache_filename
-    else:
-        return None
+    return None
 
 
 def get_hed_version_path(xml_version=None, library_name=None, local_hed_directory=None):
@@ -184,7 +189,7 @@ def get_path_from_hed_version(hed_version, library_name=None, local_hed_director
 
 
 def cache_xml_versions(hed_base_urls=DEFAULT_URL_LIST, skip_folders=DEFAULT_SKIP_FOLDERS, cache_folder=None):
-    """ Cache a file from a URL.
+    """ Cache all schemas at the given URLs.
 
     Args:
         hed_base_urls (str or list): Path or list of paths.
@@ -298,6 +303,22 @@ def _sort_version_list(hed_versions):
 
 def _get_hed_xml_versions_from_url(hed_base_url, library_name=None,
                                    skip_folders=DEFAULT_SKIP_FOLDERS, get_libraries=False):
+    """ Get all available schemas and their hash values
+
+    Args:
+        hed_base_url (str): A single GitHub API url to cache
+        library_name(str or None): If str, cache only the named library schemas
+        skip_folders (list): A list of subfolders to skip over when downloading.
+        get_libraries (bool): If true, return a dictionary of version numbers, with an entry for each library name.
+
+    Returns:
+        list or dict: List of version numbers or dictionary {library_name: [versions]}.
+
+        - The Default skip_folders is 'deprecated'.
+        - The HED cache folder defaults to HED_CACHE_DIRECTORY.
+        - The directories on Github are of the form:
+            https://api.github.com/repos/hed-standard/hed-specification/contents/hedxml
+    """
     url_request = urllib.request.urlopen(hed_base_url)
     url_data = str(url_request.read(), 'utf-8')
     loaded_json = json.loads(url_data)
@@ -375,7 +396,7 @@ def _calculate_sha1(filename):
         return None
 
 
-def _safe_copy_tmp_to_folder(temp_hed_xml_file, dest_filename):
+def _safe_move_tmp_to_folder(temp_hed_xml_file, dest_filename):
     """ Copy to destination folder and rename.
 
     Args:
@@ -385,16 +406,12 @@ def _safe_copy_tmp_to_folder(temp_hed_xml_file, dest_filename):
     Returns:
         dest_filename (str): The new filename on success or None on failure.
 
-    Notes:
-        The file will be deleted on a successful copy.
-
     """
     _, temp_xml_file = os.path.split(temp_hed_xml_file)
     dest_folder, _ = os.path.split(dest_filename)
 
     temp_filename_in_cache = os.path.join(dest_folder, temp_xml_file)
     copyfile(temp_hed_xml_file, temp_filename_in_cache)
-    os.remove(temp_hed_xml_file)
     try:
         os.replace(temp_filename_in_cache, dest_filename)
     except OSError:
@@ -413,13 +430,7 @@ def _cache_hed_version(version, library_name, version_info, cache_folder):
     if sha_hash == local_sha_hash:
         return possible_cache_filename
 
-    os.makedirs(cache_folder, exist_ok=True)
-    temp_hed_xml_file = url_to_file(download_url)
-    if temp_hed_xml_file:
-        cache_filename = _safe_copy_tmp_to_folder(temp_hed_xml_file, possible_cache_filename)
-        return cache_filename
-    else:
-        return None
+    return _cache_specific_url(download_url, possible_cache_filename)
 
 
 def _get_latest_semantic_version_in_list(semantic_version_list):
diff --git a/hed/schema/hed_schema.py b/hed/schema/hed_schema.py
index 576e6435b..063a58b4d 100644
--- a/hed/schema/hed_schema.py
+++ b/hed/schema/hed_schema.py
@@ -1,6 +1,6 @@
 
 from hed.schema.hed_schema_constants import HedKey, HedSectionKey
-from hed.util import file_util
+from hed.schema.schema_io import schema_util
 from hed.schema.schema_io.schema2xml import HedSchema2XML
 from hed.schema.schema_io.schema2wiki import HedSchema2Wiki
 
@@ -16,10 +16,7 @@ class HedSchema:
     def __init__(self):
         """ Constructor for the HedSchema class.
 
-
-        Returns:
-            HedSchema:  The constructed HED schema.
-
+            A HedSchema can be used for validation, checking tag attributes, parsing tags, etc.
         """
         self._has_duplicate_tags = False
         self.header_attributes = {}
@@ -32,7 +29,6 @@ def __init__(self):
         self._library_prefix = ""
 
         self._sections = self._create_empty_sections()
-        self.short_tag_mapping = {}
 
     # ===============================================
     # Basic schema properties
@@ -43,7 +39,6 @@ def filename(self):
 
         Returns:
             str: The filename of this schema.
-
         """
         return self._filename
 
@@ -52,7 +47,7 @@ def filename(self, value):
         """ Set the filename, if one has not already been set.
 
         Args:
-         value (str): The source filename for this file
+            value (str): The source filename for this file
         """
         if self._filename is None:
             self._filename = value
@@ -104,7 +99,7 @@ def valid_prefixes(self):
         Notes:
             - The return value is always length 1 if using a HedSchema.
         """
-        return list(self._library_prefix)
+        return [self._library_prefix]
 
     # ===============================================
     # Creation and saving functions
@@ -129,7 +124,7 @@ def get_as_xml_string(self):
         """
         schema2xml = HedSchema2XML()
         xml_tree = schema2xml.process_schema(self)
-        return file_util._xml_element_2_str(xml_tree)
+        return schema_util._xml_element_2_str(xml_tree)
 
     def save_as_xml(self):
         """ Save as XML to a temporary file.
@@ -140,7 +135,7 @@ def save_as_xml(self):
         """
         schema2xml = HedSchema2XML()
         xml_tree = schema2xml.process_schema(self)
-        local_xml_file = file_util.write_xml_tree_2_xml_file(xml_tree, ".xml")
+        local_xml_file = schema_util.write_xml_tree_2_xml_file(xml_tree, ".xml")
         return local_xml_file
 
     def save_as_mediawiki(self):
@@ -152,7 +147,7 @@ def save_as_mediawiki(self):
         """
         schema2wiki = HedSchema2Wiki()
         output_strings = schema2wiki.process_schema(self)
-        local_wiki_file = file_util.write_strings_to_file(output_strings, ".mediawiki")
+        local_wiki_file = schema_util.write_strings_to_file(output_strings, ".mediawiki")
         return local_wiki_file
 
     def set_library_prefix(self, library_prefix):
@@ -382,7 +377,7 @@ def find_tag_entry(self, tag, library_prefix=""):
             list: A list of errors while converting.
 
         Notes:
-            Works right to left (which is mostly relevant for errors).
+            Works left to right (which is mostly relevant for errors).
 
         """
         clean_tag = str(tag)
@@ -473,7 +468,7 @@ def _initialize_attributes(self, key_class):
             key_class (str): The section key for the section to update.
 
         """
-        self._sections[key_class].valid_attributes = self._get_attributes_for_class(key_class)
+        self._sections[key_class].valid_attributes = self._get_attributes_for_section(key_class)
 
     # ===============================================
     # Getters used to write out schema primarily.
@@ -625,7 +620,7 @@ def get_modifiers_for_unit(self, unit):
         valid_modifiers = self.unit_modifiers.get_entries_with_attribute(modifier_attribute_name)
         return valid_modifiers
 
-    def _get_attributes_for_class(self, key_class):
+    def _get_attributes_for_section(self, key_class):
         """ Return the valid attributes for this section.
 
         Args:
diff --git a/hed/schema/hed_schema_io.py b/hed/schema/hed_schema_io.py
index 117ab6097..1f65b9247 100644
--- a/hed/schema/hed_schema_io.py
+++ b/hed/schema/hed_schema_io.py
@@ -7,7 +7,7 @@
 from hed.schema import hed_schema_constants, hed_cache
 
 from hed.errors.exceptions import HedFileError, HedExceptions
-from hed.util import file_util
+from hed.schema.schema_io import schema_util
 
 
 def from_string(schema_string, file_type=".xml", library_prefix=None):
@@ -66,7 +66,7 @@ def load_schema(hed_path=None, library_prefix=None):
     is_url = hed_cache._check_if_url(hed_path)
 
     if is_url:
-        file_as_string = file_util.url_to_string(hed_path)
+        file_as_string = schema_util.url_to_string(hed_path)
         hed_schema = from_string(file_as_string, file_type=os.path.splitext(hed_path.lower())[1])
     elif hed_path.lower().endswith(".xml"):
         hed_schema = HedSchemaXMLParser.load_xml(hed_path)
diff --git a/hed/util/file_util.py b/hed/schema/schema_io/schema_util.py
similarity index 67%
rename from hed/util/file_util.py
rename to hed/schema/schema_io/schema_util.py
index 8dfc8ea87..5b9b08c13 100644
--- a/hed/util/file_util.py
+++ b/hed/schema/schema_io/schema_util.py
@@ -6,33 +6,6 @@
 from xml.dom import minidom
 from xml.etree import ElementTree
 
-NO_VERSION_INFO_STRING = "No version info found"
-
-
-def get_version_from_xml(hed_xml_tree):
-    """Get version from root node of an XML tree.
-
-        Args:
-            hed_xml_tree (Element):  The root node of an XML tree.
-
-        Returns:
-            str: The version of the HED schema (e.g. "8.0.0").
-
-        Raises:
-            KeyError or AttributeError: If invalid.
-
-        TODO: This should be moved to the schema module
-
-    """
-
-    if hed_xml_tree is None:
-        return NO_VERSION_INFO_STRING
-
-    try:
-        return hed_xml_tree.attrib['version']
-    except KeyError or AttributeError:
-        return NO_VERSION_INFO_STRING
-
 
 def url_to_file(resource_url):
     """ Write data from a URL resource into a file. Data is decoded as unicode.
@@ -65,22 +38,6 @@ def url_to_string(resource_url):
     return url_data
 
 
-def write_errors_to_file(issues, extension=".txt"):
-    """ Write an array of issue dictionaries to a temporary file.
-
-    Args:
-        issues (list):    List of 2-element dictionaries containing code and message keys.
-        extension (str):  Desired file extension.
-
-    Returns:
-        str: The name of the temporary file.
-    """
-    with tempfile.NamedTemporaryFile(suffix=extension, mode='w', delete=False, encoding='utf-8') as error_file:
-        for line in issues:
-            error_file.write(f"{line['code']}: {line['message']}\n")
-        return error_file.name
-
-
 def write_strings_to_file(output_strings, extension=None):
     """ Write output strings to a temporary file.
 
@@ -111,8 +68,6 @@ def write_xml_tree_2_xml_file(xml_tree, extension=".xml"):
     Returns:
         str:  Name of the temporary file.
 
-    TODO:  Should this be in the schema module?
-
     """
     with tempfile.NamedTemporaryFile(suffix=extension, mode='w', delete=False, encoding='utf-8') as hed_xml_file:
         xml_string = _xml_element_2_str(xml_tree)
@@ -129,8 +84,6 @@ def _xml_element_2_str(elem):
     Returns:
         str: An XML string representing the XML element.
 
-    TODO: Shouldn't this be with the schema?
-
     """
     rough_string = ElementTree.tostring(elem, method='xml')
     reparsed = minidom.parseString(rough_string)
diff --git a/hed/tools/analysis/file_dictionary.py b/hed/tools/analysis/file_dictionary.py
index 0deb1979b..ef0a0da8e 100644
--- a/hed/tools/analysis/file_dictionary.py
+++ b/hed/tools/analysis/file_dictionary.py
@@ -131,6 +131,9 @@ def make_file_dict(file_list, key_indices=(0, 2), separator='_'):
             key_indices (tuple): Positions of entities to use for key.
             separator (str):  Separator character used to construct key.
 
+        Returns:
+            dict: Key is based on key indices and value is a full path.
+
         """
         file_dict = {}
         for the_file in file_list:
diff --git a/hed/tools/bids/bids_file_group.py b/hed/tools/bids/bids_file_group.py
index 6124c68d1..5dd3f87fa 100644
--- a/hed/tools/bids/bids_file_group.py
+++ b/hed/tools/bids/bids_file_group.py
@@ -46,7 +46,7 @@ def __init__(self, root_path, suffix="_events", obj_type="tabular"):
         for bids_obj in self.datafile_dict.values():
             sidecar_list = self.get_sidecars_from_path(bids_obj)
             if sidecar_list:
-                bids_obj.sidecar = sidecar_list[-1]
+                bids_obj.sidecar = self.sidecar_dict[sidecar_list[-1]]
 
     def get_sidecars_from_path(self, obj):
         """ Return applicable sidecars for the object.
@@ -55,7 +55,7 @@ def get_sidecars_from_path(self, obj):
             obj (BidsTabularFile or BidsSidecarFile):  The BIDS file object to get the sidecars for.
 
         Returns:
-            list:  A list of the applicable sidecars for obj starting at the root.
+            list:  A list of the paths for applicable sidecars for obj starting at the root.
 
         """
         sidecar_list = []
@@ -64,7 +64,7 @@ def get_sidecars_from_path(self, obj):
             current_path = os.path.realpath(os.path.join(current_path, comp))
             next_sidecar = self._get_sidecar_for_obj(obj, current_path)
             if next_sidecar:
-                sidecar_list.append(next_sidecar)
+                sidecar_list.append(next_sidecar.file_path)
         return sidecar_list
 
     def _get_sidecar_for_obj(self, obj, current_path):
diff --git a/hed/tools/bids/bids_sidecar_file.py b/hed/tools/bids/bids_sidecar_file.py
index fa2db933a..cf4a493d7 100644
--- a/hed/tools/bids/bids_sidecar_file.py
+++ b/hed/tools/bids/bids_sidecar_file.py
@@ -1,7 +1,4 @@
 import os
-import io
-import json
-from hed.errors import HedFileError
 from hed.models.sidecar import Sidecar
 from hed.tools.bids.bids_file import BidsFile
 
@@ -42,58 +39,23 @@ def set_contents(self, content_info=None, overwrite=False):
         """ Set the contents of the sidecar.
 
         Args:
-            content_info (list, str, dict or None): If None, create a Sidecar from the objects file-path.
+            content_info (list, str, or None): If None, create a Sidecar from the object's file-path.
             overwrite (bool): If True, overwrite contents if already set.
 
         Notes:
             - The handling of content_info is as follows:
-                - None: This object's file_path is used to read a JSON dictionary from.
-                - str:  The string is interpreted as a path and used to read a JSON dictionary from.
-                - list: The list is of paths and/or BidsSideCarFiles and a merged dictionary is created.
+                - None: This object's file_path is used.
+                - str:  The string is interpreted as a path of the JSON.
+                - list: The list is of paths.
 
          """
         if not overwrite and self.contents:
             return
         if not content_info:
-            file_contents = self.get_merged([self.file_path])
-        elif isinstance(content_info, str):
-            file_contents = self.get_merged([content_info])
-        elif isinstance(content_info, list):
-            file_contents = self.get_merged(content_info)
-        elif isinstance(content_info, dict):
-            file_contents = content_info
-        else:
-            raise HedFileError("InvalidJSONSidecarContents",
-                               f"Attempt to set {self.file_path} to invalid {str(content_info)}", "")
-        self.has_hed = self.is_hed(file_contents)
-        self.contents = Sidecar(file=io.StringIO(json.dumps(file_contents)),
+            content_info = self.file_path
+        self.contents = Sidecar(files=content_info,
                                 name=os.path.realpath(os.path.basename(self.file_path)))
-
-    @staticmethod
-    def get_merged(file_list):
-        """ Return merged contents of JSON files as dict.
-
-        Args:
-            file_list (list or None):  A list of JSON files representing sidecars in the order they are to be merged.
-
-        Returns:
-            dict:  A merged JSON dictionary.
-
-        Notes:
-            - Merging takes place from front to back with overwriting of top-level keys.
-
-        """
-        merged_dict = {}
-        if not file_list:
-            return merged_dict
-        for file in file_list:
-            if isinstance(file, BidsSidecarFile):
-                file = file.file_path
-            with open(file, 'r') as fp:
-                next_sidecar = json.load(fp)
-            for key, item in next_sidecar.items():
-                merged_dict[key] = item
-        return merged_dict
+        self.has_hed = self.is_hed(self.contents.loaded_dict)
 
     @staticmethod
     def is_hed(json_dict):
diff --git a/hed/tools/bids/bids_tabular_file.py b/hed/tools/bids/bids_tabular_file.py
index c9533a533..a7a99a35b 100644
--- a/hed/tools/bids/bids_tabular_file.py
+++ b/hed/tools/bids/bids_tabular_file.py
@@ -18,7 +18,7 @@ def set_contents(self, content_info=None, overwrite=False):
         """ Set the contents of this tabular file.
 
         Args:
-            content_info (str or None):   If string should be a file path if None use self.file_path.
+            content_info (None):   This always uses the internal file_path to create the contents.
             overwrite:  If False, do not overwrite existing contents if any.
 
         """
diff --git a/hed/tools/bids/bids_timeseries_file.py b/hed/tools/bids/bids_timeseries_file.py
index f6c742746..07043f1c0 100644
--- a/hed/tools/bids/bids_timeseries_file.py
+++ b/hed/tools/bids/bids_timeseries_file.py
@@ -1,5 +1,5 @@
 import os
-from hed.models.tabular_input import TabularInput
+from hed.models.timeseries_input import TimeseriesInput
 from hed.tools.bids.bids_file import BidsFile
 
 
@@ -10,8 +10,24 @@ def __init__(self, file_path):
         """ Constructor for a BIDS events file. """
         super().__init__(file_path)
 
-    def set_contents(self, content_info=None, no_overwrite=True):
-        if self.contents and no_overwrite:
-            return
-        BidsFile.set_contents(self, content_info=TabularInput(file=self.file_path, sidecar=self.sidecar,
-                                                              name=os.path.realpath(self.file_path)))
+    def set_contents(self, content_info=None, overwrite=False):
+        """ Set the contents of this timeseries file.
+
+        Args:
+            content_info (None):   This always uses the internal file_path to create the contents.
+            overwrite:  If False, do not overwrite existing contents if any.
+
+        """
+        # if self.contents and not overwrite:
+        #     return
+        #
+        # if self.sidecar:
+        #     self.contents = TimeseriesInput(file=self.file_path, sidecar=self.sidecar.contents,
+        #                                     name=os.path.realpath(self.file_path))
+        #     if self.sidecar.has_hed:
+        #         self.has_hed = True
+        # else:
+        #     self.contents = TimeseriesInput(file=self.file_path, name=os.path.realpath(self.file_path))
+        # columns = self.contents._mapper._column_map.values()
+        # if 'HED' in columns or 'HED_assembled' in columns:
+        #     self.has_hed = True
\ No newline at end of file
diff --git a/setup.cfg b/setup.cfg
index 0e334ca31..9cb2e9070 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -8,7 +8,7 @@ long_description = file: README.md
 long_description_content_type = text/markdown
 url = https://github.com/hed-standard/hed-python/
 # project_urls =
-#     Bug Tracker = https://github.com/pypa/sampleproject/issues
+bug Tracker = https://github.com/hed-standard/hed-python/issues
 classifiers =
     Programming Language :: Python :: 3
     License :: OSI Approved :: MIT License
@@ -22,7 +22,6 @@ package_dir =
 packages = find_namespace:
 python_requires = >=3.7
 install_requires =
-    openpyxl
     defusedxml
     et-xmlfile
     inflect


Test patch:
diff --git a/hed/cli/cmdtest.py b/hed/cli/cmdtest.py
deleted file mode 100644
index 63dd369cb..000000000
--- a/hed/cli/cmdtest.py
+++ /dev/null
@@ -1,24 +0,0 @@
-import os
-import subprocess
-
-
-if __name__ == '__main__':
-    # try:
-    #     proc = subprocess.Popen(['docker', 'version'], stdout=subprocess.PIPE,
-    #                             stderr=subprocess.PIPE, bufsize=1)
-    #     print(proc.communicate())
-    # except OSError as ex:
-    #     print(f"to here{ex}")
-
-    try:
-        proc = subprocess.Popen(['docker', 'version'], stdout=subprocess.PIPE,
-                                stderr=subprocess.PIPE, bufsize=1)
-
-        output = proc.stdout.readlines()
-        print(output)
-        print(f"\n\nNow print by line (output contains {len(output)} lines):")
-        for out in output:
-            print(f"{str(out, 'utf-8')}")
-
-    except OSError as ex:
-        print(f"to here{ex}")
\ No newline at end of file
diff --git a/tests/data/sidecar_tests/json_errors_minor.json b/tests/data/sidecar_tests/json_errors_minor.json
new file mode 100644
index 000000000..d4f5e8f4b
--- /dev/null
+++ b/tests/data/sidecar_tests/json_errors_minor.json
@@ -0,0 +1,33 @@
+{
+    "onset": {
+        "Description": "Onset of the go signal which is the disappearance of the cue bar",
+        "Units": "s"
+    },
+	"error_blank_hed_string": {
+		"Description": "Subject presses both buttons causing cue bar to start count-down display",
+		"Levels" : {
+            "cue": "Subject presses both buttons causing cue bar to start count-down display",
+            "go": "Bar disappears cueing the subject to make a choice about which hand to raise",
+            "right-raised": "Subject releases right button and raises right hand",
+            "left-raised": "Subject releases left button and raises left hand",
+            "left-raised-match": "Subject raised left hand and computer presented image of left hand.",
+            "left-raised-nomatch": "Subject raised left hand and computer presented image of right hand.",
+            "right-raised-match": "Subject raised right hand and computer presented image of right hand.",
+            "right-raised-nomatch": "Subject raised right hand and computer presented image of left hand."
+        },
+        "HED": {
+          "cue": "",
+          "go": "Description/Onset of the go signal which is the disappearance of the cue bar, Sensory-event, (Visual, Experimental-stimulus, Instructional, Rectangle/#)",
+          "right-raised": "Description/Subject raised right hand thereby releasing right button, (Agent-action, Experimental-participant, (Lift, (Right-side, Hand)), (Release, (Right-side, Button)))",
+          "left-raised": "Description/Subject raised left hand thereby releasing left button, (Agent-action, Experimental-participant, (Lift, (Left-side, Hand)), (Release, (Left-side, Button)))",
+          "left-raised-match": "Description/Computer presented image of left hand thereby matching a previously raised left hand, (Sensory-event, Visual, (Drawing, ID/left_hand.png,(Left-side, Hand)), (Feedback, (Intended-effect, Penalty)))",
+          "left-raised-nomatch": "Description/Computer presented image of left hand thereby not matching previously raised right hand, (Sensory-event, Visual, (Drawing, ID/right_hand.png, (Left-side, Hand)), (Feedback, (Intended-effect, Reward)))",
+          "right-raised-match": "Description/Computer presented image of right hand thereby matching a previously raised right hand, (Sensory-event, Visual, (Drawing, ID/right_hand.png,(Right-side, Hand)), (Feedback, (Intended-effect, Penalty)))",
+          "right-raised-nomatch": "Description/Computer presented image of right hand thereby not matching a previously raised left hand, (Sensory-event, Visual, (Drawing, ID/right_hand.png, (Right-side, Hand)), (Feedback, (Intended-effect, Reward)))"
+        }
+	},
+	"trial": {
+		"Description": "The number of the trial in the experiment.",
+		"HED": "Description/The number of the trial in the experiment, (Experimental-trial, Item-count/#)"
+	}
+}
\ No newline at end of file
diff --git a/tests/data/sidecar_tests/test_merged1.json b/tests/data/sidecar_tests/test_merged1.json
new file mode 100644
index 000000000..29284b083
--- /dev/null
+++ b/tests/data/sidecar_tests/test_merged1.json
@@ -0,0 +1,14 @@
+{
+   "response_time": {
+       "LongName": "Response time after stimulus",
+       "Description": "Time from stimulus presentation until subject presses button",
+       "Units": "ms",
+       "HED": "Action/Button press/#"
+   },
+   "response_time2": {
+       "LongName": "Response time after stimulus",
+       "Description": "Time from stimulus presentation until subject presses button",
+       "Units": "ms",
+       "HED": "Action/Button press/#"
+   }
+}
\ No newline at end of file
diff --git a/tests/data/sidecar_tests/test_merged2.json b/tests/data/sidecar_tests/test_merged2.json
new file mode 100644
index 000000000..c1900b4f1
--- /dev/null
+++ b/tests/data/sidecar_tests/test_merged2.json
@@ -0,0 +1,25 @@
+{
+   "trial_type": {
+       "LongName": "Event category",
+       "Description": "Indicator of type of action that is expected",
+       "Levels": {
+          "go": "A red square is displayed to indicate starting",
+          "stop": "A blue square is displayed to indicate stopping"
+       },
+       "HED": {
+          "go": "Item/ItemTag1",
+          "stop": "Item/ItemTag2"
+       }
+   },
+   "stim_file": {
+       "LongName": "Stimulus file name",
+       "Description": "Relative path of the stimulus image file",
+       "HED": "Attribute/File/#"
+   },
+   "response_time2": {
+       "LongName": "Response time after stimulus",
+       "Description": "Time from stimulus presentation until subject presses button",
+       "Units": "ms",
+       "HED": "Action/Button press/ValueReplaced/#"
+   }
+}
\ No newline at end of file
diff --git a/tests/data/sidecar_tests/test_merged_merged.json b/tests/data/sidecar_tests/test_merged_merged.json
new file mode 100644
index 000000000..8e8047585
--- /dev/null
+++ b/tests/data/sidecar_tests/test_merged_merged.json
@@ -0,0 +1,31 @@
+{
+   "trial_type": {
+       "LongName": "Event category",
+       "Description": "Indicator of type of action that is expected",
+       "Levels": {
+          "go": "A red square is displayed to indicate starting",
+          "stop": "A blue square is displayed to indicate stopping"
+       },
+       "HED": {
+          "go": "Item/ItemTag1",
+          "stop": "Item/ItemTag2"
+       }
+   },
+   "response_time": {
+       "LongName": "Response time after stimulus",
+       "Description": "Time from stimulus presentation until subject presses button",
+       "Units": "ms",
+       "HED": "Action/Button press/#"
+   },
+   "stim_file": {
+       "LongName": "Stimulus file name",
+       "Description": "Relative path of the stimulus image file",
+       "HED": "Attribute/File/#"
+   },
+   "response_time2": {
+       "LongName": "Response time after stimulus",
+       "Description": "Time from stimulus presentation until subject presses button",
+       "Units": "ms",
+       "HED": "Action/Button press/ValueReplaced/#"
+   }
+}
\ No newline at end of file
diff --git a/tests/errors/test_error_reporter.py b/tests/errors/test_error_reporter.py
index 73ed8ffd2..e90dff80e 100644
--- a/tests/errors/test_error_reporter.py
+++ b/tests/errors/test_error_reporter.py
@@ -86,7 +86,8 @@ def test_printable_issue_string(self):
         printable_issues2 = get_printable_issue_string(error_list, severity=ErrorSeverity.ERROR)
         self.assertTrue(len(printable_issues) > len(printable_issues2))
 
-        printable_issues3 = get_printable_issue_string(error_list, severity=ErrorSeverity.ERROR, title="Later added custom title that is longer")
+        printable_issues3 = get_printable_issue_string(error_list, severity=ErrorSeverity.ERROR,
+                                                       title="Later added custom title that is longer")
         self.assertTrue(len(printable_issues3) > len(printable_issues2))
 
-        self.error_handler.reset_error_context()
\ No newline at end of file
+        self.error_handler.reset_error_context()
diff --git a/tests/models/test_base_file_input.py b/tests/models/test_base_file_input.py
index 53b46fa93..477985bce 100644
--- a/tests/models/test_base_file_input.py
+++ b/tests/models/test_base_file_input.py
@@ -8,8 +8,9 @@
 class Test(unittest.TestCase):
     @classmethod
     def setUpClass(cls):
-        cls.base_output_folder = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../data/tests_output/")
-        os.makedirs(cls.base_output_folder, exist_ok=True)
+        base_output = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../data/tests_output/")
+        cls.base_output_folder = base_output
+        os.makedirs(base_output, exist_ok=True)
 
     @classmethod
     def tearDownClass(cls):
@@ -22,7 +23,8 @@ def tearDownClass(cls):
     #                                '../data/validator_tests/bids_events_bad_column_name.tsv')
     #
     #     hed_schema = schema.load_schema(schema_path)
-    #     json_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../data/validator_tests/bids_events.json")
+    #     json_path = os.path.join(os.path.dirname(os.path.abspath(__file__)),
+    #                                              "../data/validator_tests/bids_events.json")
     #     validator = HedValidator(hed_schema=hed_schema)
     #     sidecar = Sidecar(json_path)
     #     issues = sidecar.validate_entries(validator)
@@ -41,7 +43,8 @@ def tearDownClass(cls):
     #                                '../data/validator_tests/bids_events_bad_category_key.tsv')
     #
     #     hed_schema = schema.load_schema(schema_path)
-    #     json_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../data/validator_tests/bids_events.json")
+    #     json_path = os.path.join(os.path.dirname(os.path.abspath(__file__)),
+    #                                              "../data/validator_tests/bids_events.json")
     #     validator = HedValidator(hed_schema=hed_schema)
     #     sidecar = Sidecar(json_path)
     #     issues = sidecar.validate_entries(validator)
diff --git a/tests/models/test_column_mapper.py b/tests/models/test_column_mapper.py
index 30587d88a..f0aef2b45 100644
--- a/tests/models/test_column_mapper.py
+++ b/tests/models/test_column_mapper.py
@@ -3,7 +3,8 @@
 
 from hed.models import ColumnMapper, ColumnType, ColumnMetadata, HedString, model_constants
 from hed.schema import load_schema
-from hed import HedFileError
+from hed.models.sidecar import Sidecar
+
 
 class Test(unittest.TestCase):
     schema_file = '../data/schema_test_data/HED8.0.0t.xml'
@@ -77,12 +78,12 @@ def test_optional_column(self):
 
     def test_add_json_file_events(self):
         mapper = ColumnMapper()
-        mapper.add_sidecars(self.basic_events_json)
+        mapper._set_sidecar(Sidecar(self.basic_events_json))
         self.assertTrue(len(mapper.column_data) >= 2)
 
     def test__detect_event_type(self):
         mapper = ColumnMapper()
-        mapper.add_sidecars(self.basic_events_json)
+        mapper._set_sidecar(Sidecar(self.basic_events_json))
         self.assertTrue(mapper.column_data[self.basic_event_name].column_type == self.basic_event_type)
 
     def test_add_attribute_columns(self):
@@ -118,14 +119,14 @@ def test__finalize_mapping(self):
 
     def test_expand_column(self):
         mapper = ColumnMapper()
-        mapper.add_sidecars(self.basic_events_json)
+        mapper._set_sidecar(Sidecar(self.basic_events_json))
         mapper.set_column_map(self.basic_column_map)
         expanded_column = mapper._expand_column(2, "go")
         self.assertTrue(isinstance(expanded_column[0], HedString))
 
     def test_expand_row_tags(self):
         mapper = ColumnMapper()
-        mapper.add_sidecars(self.basic_events_json)
+        mapper._set_sidecar(Sidecar(self.basic_events_json))
         mapper.add_columns(self.basic_attribute_column)
         mapper.set_column_map(self.basic_column_map)
         expanded_row = mapper.expand_row_tags(self.basic_event_row)
@@ -134,7 +135,7 @@ def test_expand_row_tags(self):
 
     def test_expansion_issues(self):
         mapper = ColumnMapper()
-        mapper.add_sidecars(self.basic_events_json)
+        mapper._set_sidecar(Sidecar(self.basic_events_json))
         mapper.add_columns(self.basic_attribute_column)
         mapper.set_column_map(self.basic_column_map)
         expanded_row = mapper.expand_row_tags(self.basic_event_row_invalid)
@@ -189,5 +190,6 @@ def test_add_prefix_verify_short_tag_read(self):
             prepended_hed_string = expanded_row[model_constants.COLUMN_TO_HED_TAGS][0]
             self.assertEqual(expected_result, str(prepended_hed_string))
 
+
 if __name__ == '__main__':
     unittest.main()
diff --git a/tests/models/test_def_dict.py b/tests/models/test_def_dict.py
index 48e0535ab..cb47957d4 100644
--- a/tests/models/test_def_dict.py
+++ b/tests/models/test_def_dict.py
@@ -94,7 +94,8 @@ def test_definitions(self):
             'defInGroup': ErrorHandler.format_error(DefinitionErrors.DEF_TAG_IN_DEFINITION,
                                                     tag=HedTag("Def/ImproperlyPlacedDef"), def_name="ValidDefName"),
             'defExpandInGroup': ErrorHandler.format_error(DefinitionErrors.DEF_TAG_IN_DEFINITION,
-                                                    tag=HedTag("Def-expand/ImproperlyPlacedDef"), def_name="ValidDefName")
+                                                          tag=HedTag("Def-expand/ImproperlyPlacedDef"),
+                                                          def_name="ValidDefName")
         }
 
         self.check_def_base(test_strings, expected_results)
diff --git a/tests/models/test_def_mapper.py b/tests/models/test_def_mapper.py
index dde896ff0..d5c4f8e8c 100644
--- a/tests/models/test_def_mapper.py
+++ b/tests/models/test_def_mapper.py
@@ -27,7 +27,8 @@ def setUpClass(cls):
         cls.placeholder_label_def_string = "Def/TestDefPlaceholder/2471"
         cls.placeholder_definition_contents = "(Item/TestDef1/#,Item/TestDef2)"
         cls.placeholder_definition_string = f"(Definition/TestDefPlaceholder/#,{cls.placeholder_definition_contents})"
-        cls.placeholder_definition_string_no_paren = f"Definition/TestDefPlaceholder/#,{cls.placeholder_definition_contents}"
+        cls.placeholder_definition_string_no_paren = \
+            f"Definition/TestDefPlaceholder/#,{cls.placeholder_definition_contents}"
         cls.placeholder_expanded_def_string = "(Def-expand/TestDefPlaceholder/2471,(Item/TestDef1/2471,Item/TestDef2))"
 
         cls.placeholder_hed_string_with_def = f"{cls.basic_hed_string},{cls.placeholder_label_def_string}"
@@ -53,7 +54,8 @@ def setUpClass(cls):
             'str7': False,
         }
 
-    def base_def_validator(self, test_strings, result_strings, valid_strings, expand_defs, shrink_defs, remove_definitions, extra_ops=None,
+    def base_def_validator(self, test_strings, result_strings, valid_strings, expand_defs, shrink_defs,
+                           remove_definitions, extra_ops=None,
                            basic_definition_string=None):
         if not basic_definition_string:
             basic_definition_string = self.basic_definition_string
@@ -71,7 +73,8 @@ def base_def_validator(self, test_strings, result_strings, valid_strings, expand
         for key in test_strings:
             string, expected_result, invalid = test_strings[key], result_strings[key], valid_strings[key]
             test_string = HedString(string)
-            def_issues = test_string.validate(hed_ops, expand_defs=expand_defs, shrink_defs=shrink_defs, remove_definitions=remove_definitions)
+            def_issues = test_string.validate(hed_ops, expand_defs=expand_defs, shrink_defs=shrink_defs,
+                                              remove_definitions=remove_definitions)
             self.assertEqual(invalid, bool(def_issues))
             self.assertEqual(test_string.get_as_short(), expected_result)
 
@@ -104,17 +107,27 @@ def test_expand_def_tags(self):
             'str7': "(" + self.expanded_def_string + "," + self.basic_hed_string + ")"
         }
 
-        self.base_def_validator(basic_def_strings, expanded_def_strings_with_definition, self.mark_all_as_valid_strings, expand_defs=True, shrink_defs=False, remove_definitions=False)
-        self.base_def_validator(basic_def_strings, basic_def_strings, self.mark_all_as_valid_strings, expand_defs=False, shrink_defs=False, remove_definitions=False)
-        self.base_def_validator(basic_def_strings, basic_def_strings, self.mark_all_as_valid_strings, expand_defs=False, shrink_defs=True, remove_definitions=False)
-        self.base_def_validator(expanded_def_strings_with_definition, basic_def_strings, self.mark_all_as_valid_strings, expand_defs=False, shrink_defs=True, remove_definitions=False)
-        self.base_def_validator(expanded_def_strings_with_definition, expanded_def_strings_with_definition, self.mark_all_as_valid_strings, expand_defs=True, shrink_defs=False, remove_definitions=False)
-        self.base_def_validator(basic_def_strings, expanded_def_strings, self.mark_all_as_valid_strings, expand_defs=True, shrink_defs=False, remove_definitions=True)
+        self.base_def_validator(basic_def_strings, expanded_def_strings_with_definition,
+                                self.mark_all_as_valid_strings, expand_defs=True,
+                                shrink_defs=False, remove_definitions=False)
+        self.base_def_validator(basic_def_strings, basic_def_strings, self.mark_all_as_valid_strings,
+                                expand_defs=False, shrink_defs=False, remove_definitions=False)
+        self.base_def_validator(basic_def_strings, basic_def_strings, self.mark_all_as_valid_strings,
+                                expand_defs=False, shrink_defs=True, remove_definitions=False)
+        self.base_def_validator(expanded_def_strings_with_definition, basic_def_strings,
+                                self.mark_all_as_valid_strings, expand_defs=False, shrink_defs=True,
+                                remove_definitions=False)
+        self.base_def_validator(expanded_def_strings_with_definition, expanded_def_strings_with_definition,
+                                self.mark_all_as_valid_strings, expand_defs=True, shrink_defs=False,
+                                remove_definitions=False)
+        self.base_def_validator(basic_def_strings, expanded_def_strings, self.mark_all_as_valid_strings,
+                                expand_defs=True, shrink_defs=False, remove_definitions=True)
 
         validator = HedValidator(self.hed_schema)
         extra_ops = [validator]
 
-        self.base_def_validator(basic_def_strings, expanded_def_strings_with_definition, self.valid_definition_strings, expand_defs=True, shrink_defs=False,
+        self.base_def_validator(basic_def_strings, expanded_def_strings_with_definition,
+                                self.valid_definition_strings, expand_defs=True, shrink_defs=False,
                                 extra_ops=extra_ops, remove_definitions=False)
 
     # special case test
diff --git a/tests/models/test_hed_group.py b/tests/models/test_hed_group.py
index b0bff2cd7..8f0898872 100644
--- a/tests/models/test_hed_group.py
+++ b/tests/models/test_hed_group.py
@@ -4,6 +4,7 @@
 from hed import schema
 from hed.models import HedString
 
+
 class Test(unittest.TestCase):
 
     @classmethod
@@ -17,7 +18,8 @@ def test_remove_groups(self):
         basic_definition_string = "(Definition/TestDef, (Keypad-key/TestDef1,Keyboard-key/TestDef2))"
         basic_definition_string_repeated = f"{basic_definition_string},{basic_definition_string}"
         def_string_with_repeat = HedString(basic_definition_string_repeated, self.hed_schema)
-        definition_tags = def_string_with_repeat.find_tags({DefTagNames.DEFINITION_KEY}, recursive=True, include_groups=1)
+        definition_tags = def_string_with_repeat.find_tags({DefTagNames.DEFINITION_KEY},
+                                                           recursive=True, include_groups=1)
         definition_tag2 = definition_tags[1]
         def_string_with_repeat.remove([definition_tag2])
         remaining_children = def_string_with_repeat.get_all_groups()
@@ -25,9 +27,11 @@ def test_remove_groups(self):
             if child is definition_tag2:
                 self.assertFalse(False, "Definition tag not removed successfully")
 
-        basic_definition_string_repeated_subgroup = f"{basic_definition_string},{basic_definition_string}, ({basic_definition_string})"
+        basic_definition_string_repeated_subgroup = \
+            f"{basic_definition_string},{basic_definition_string}, ({basic_definition_string})"
         def_string_with_repeat = HedString(basic_definition_string_repeated_subgroup, self.hed_schema)
-        definition_tags = def_string_with_repeat.find_tags({DefTagNames.DEFINITION_KEY}, recursive=True, include_groups=1)
+        definition_tags = def_string_with_repeat.find_tags({DefTagNames.DEFINITION_KEY},
+                                                           recursive=True, include_groups=1)
         definition_tag3 = definition_tags[2]
         def_string_with_repeat.remove([definition_tag3])
         remaining_children = def_string_with_repeat.get_all_groups()
@@ -35,9 +39,11 @@ def test_remove_groups(self):
             if child is definition_tag3:
                 self.assertFalse(False, "Nested definition tag not removed successfully")
 
-        basic_definition_string_repeated_subgroup = f"{basic_definition_string},{basic_definition_string}, ({basic_definition_string})"
+        basic_definition_string_repeated_subgroup = \
+            f"{basic_definition_string},{basic_definition_string}, ({basic_definition_string})"
         def_string_with_repeat = HedString(basic_definition_string_repeated_subgroup, self.hed_schema)
-        definition_tags = def_string_with_repeat.find_tags({DefTagNames.DEFINITION_KEY}, recursive=True, include_groups=1)
+        definition_tags = def_string_with_repeat.find_tags({DefTagNames.DEFINITION_KEY},
+                                                           recursive=True, include_groups=1)
         definition_tag2 = definition_tags[1]
         def_string_with_repeat.remove([definition_tag2])
         remaining_children = def_string_with_repeat.get_all_groups()
@@ -45,9 +51,9 @@ def test_remove_groups(self):
             if child is definition_tag2:
                 self.assertFalse(False, "Nested definition tag not removed successfully")
 
-
     def test_find_tags_with_term(self):
-        basic_hed_string = "(Keypad-key/TestDef1,Keyboard-key/TestDef2, Item/Object, Event), Event, Object, Geometric-object"
+        basic_hed_string = \
+            "(Keypad-key/TestDef1,Keyboard-key/TestDef2, Item/Object, Event), Event, Object, Geometric-object"
         basic_hed_string_obj = HedString(basic_hed_string, self.hed_schema)
         # works
         located_tags = basic_hed_string_obj.find_tags_with_term("Object", recursive=True, include_groups=0)
diff --git a/tests/models/test_hed_string_comb.py b/tests/models/test_hed_string_comb.py
index dfa619a1c..03fdd75f7 100644
--- a/tests/models/test_hed_string_comb.py
+++ b/tests/models/test_hed_string_comb.py
@@ -65,4 +65,3 @@ def test_replace(self):
         self.assertEqual(len(string1.get_all_tags()), 1)
         self.assertEqual(len(string2.get_all_tags()), 3)
         self.assertEqual(len(comb_string.get_all_tags()), 4)
-
diff --git a/tests/models/test_sidecar.py b/tests/models/test_sidecar.py
index 1037aae9a..c88f9f692 100644
--- a/tests/models/test_sidecar.py
+++ b/tests/models/test_sidecar.py
@@ -21,9 +21,11 @@ def setUpClass(cls):
         cls.json_without_definitions_filename = \
             os.path.join(cls.base_data_dir, "sidecar_tests/both_types_events_without_definitions.json")
         cls.json_errors_filename = os.path.join(cls.base_data_dir, "sidecar_tests/json_errors.json")
+        cls.json_errors_filename_minor = os.path.join(cls.base_data_dir, "sidecar_tests/json_errors_minor.json")
         cls.default_sidecar = Sidecar(cls.json_filename)
         cls.json_def_sidecar = Sidecar(cls.json_def_filename)
         cls.errors_sidecar = Sidecar(cls.json_errors_filename)
+        cls.errors_sidecar_minor = Sidecar(cls.json_errors_filename_minor)
         cls.json_without_definitions_sidecar = Sidecar(cls.json_without_definitions_filename)
 
         cls.base_output_folder = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../data/tests_output/")
@@ -43,14 +45,14 @@ def test_invalid_filenames(self):
             json_dict = Sidecar(None)
         except HedFileError:
             pass
-        self.assertTrue(len(json_dict._column_data) == 0)
+        self.assertTrue(len(json_dict.loaded_dict) == 0)
 
         json_dict = None
         try:
             json_dict = Sidecar("")
         except HedFileError:
             pass
-        self.assertTrue(len(json_dict._column_data) == 0)
+        self.assertTrue(len(json_dict.loaded_dict) == 0)
 
     def test_name(self):
         invalid_json = "invalidxmlfile.json"
@@ -78,14 +80,17 @@ def test__iter__(self):
 
     def test_validate_column_group(self):
         validator = HedValidator(hed_schema=None)
-        validation_issues = self.json_def_sidecar.validate_entries(validator, check_for_warnings=True)
-        self.assertEqual(len(validation_issues), 0)
-
-        validation_issues = self.default_sidecar.validate_entries(validator, check_for_warnings=True)
-        self.assertEqual(len(validation_issues), 0)
+        # validation_issues = self.json_def_sidecar.validate_entries(validator, check_for_warnings=True)
+        # self.assertEqual(len(validation_issues), 0)
+        #
+        # validation_issues = self.default_sidecar.validate_entries(validator, check_for_warnings=True)
+        # self.assertEqual(len(validation_issues), 0)
 
         validation_issues = self.errors_sidecar.validate_entries(validator, check_for_warnings=True)
-        self.assertEqual(len(validation_issues), 15)
+        self.assertEqual(len(validation_issues), 4)
+
+        validation_issues2 = self.errors_sidecar_minor.validate_entries(validator, check_for_warnings=True)
+        self.assertEqual(len(validation_issues2), 10)
 
         validation_issues = self.json_without_definitions_sidecar.validate_entries(validator, check_for_warnings=True)
         self.assertEqual(len(validation_issues), 1)
@@ -125,5 +130,17 @@ def test_save_load2(self):
         for str1, str2 in zip(sidecar.hed_string_iter(), reloaded_sidecar.hed_string_iter()):
             self.assertEqual(str1, str2)
 
+    def test_merged_sidecar(self):
+        base_folder = self.base_data_dir + "sidecar_tests/"
+        combined_sidecar_json = base_folder + "test_merged_merged.json"
+        sidecar_json1 = base_folder + "test_merged1.json"
+        sidecar_json2 = base_folder + "test_merged2.json"
+
+        sidecar = Sidecar([sidecar_json1, sidecar_json2])
+        sidecar2 = Sidecar(combined_sidecar_json)
+
+        self.assertEqual(sidecar.loaded_dict, sidecar2.loaded_dict)
+
+
 if __name__ == '__main__':
     unittest.main()
diff --git a/tests/models/test_tabular_input.py b/tests/models/test_tabular_input.py
index c1ac15abd..63f5262a1 100644
--- a/tests/models/test_tabular_input.py
+++ b/tests/models/test_tabular_input.py
@@ -26,7 +26,8 @@ def test_missing_column_name_issue(self):
                                    '../data/validator_tests/bids_events_bad_column_name.tsv')
 
         hed_schema = schema.load_schema(schema_path)
-        json_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../data/validator_tests/bids_events.json")
+        json_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                 "../data/validator_tests/bids_events.json")
         validator = HedValidator(hed_schema=hed_schema)
         sidecar = Sidecar(json_path)
         issues = sidecar.validate_entries(validator)
@@ -38,7 +39,6 @@ def test_missing_column_name_issue(self):
         validation_issues = input_file.validate_file(validator, check_for_warnings=True)
         self.assertEqual(len(validation_issues), 1)
 
-
     def test_expand_column_issues(self):
         schema_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),
                                    '../data/validator_tests/bids_schema.mediawiki')
@@ -46,14 +46,15 @@ def test_expand_column_issues(self):
                                    '../data/validator_tests/bids_events_bad_category_key.tsv')
 
         hed_schema = schema.load_schema(schema_path)
-        json_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../data/validator_tests/bids_events.json")
+        json_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                 "../data/validator_tests/bids_events.json")
         validator = HedValidator(hed_schema=hed_schema)
         sidecar = Sidecar(json_path)
         issues = sidecar.validate_entries(validator)
         self.assertEqual(len(issues), 0)
         input_file = TabularInput(events_path, sidecar=sidecar)
 
-        #Fix whatever is wrong with onset tag here.  It's thinking Description/Onset continues is an invalid tag???'
+        # Fix whatever is wrong with onset tag here.  It's thinking Description/Onset continues is an invalid tag???'
         validation_issues = input_file.validate_file_sidecars(validator)
         self.assertEqual(len(validation_issues), 0)
         validation_issues = input_file.validate_file(validator, check_for_warnings=True)
diff --git a/tests/models/test_timeseries_input.py b/tests/models/test_timeseries_input.py
index 4c740210f..c01f13301 100644
--- a/tests/models/test_timeseries_input.py
+++ b/tests/models/test_timeseries_input.py
@@ -2,9 +2,7 @@
 import os
 import shutil
 
-from hed.models import Sidecar, TimeseriesInput
-from hed import schema
-from hed.validator import HedValidator
+from hed.models import TimeseriesInput
 
 # TODO: Add tests about correct handling of 'n/a'
 
diff --git a/tests/schema/test_convert_tags.py b/tests/schema/test_convert_tags.py
index 058d23fd3..db5fea76f 100644
--- a/tests/schema/test_convert_tags.py
+++ b/tests/schema/test_convert_tags.py
@@ -66,7 +66,8 @@ def test_tag(self):
 
     def test_tag_takes_value(self):
         test_strings = {
-            # these should all be allowed by initial identifiers.  Validators should flag the second two for '/' not being an allowed char.
+            # these should all be allowed by initial identifiers.
+            # Validators should flag the second two for '/' not being an allowed char.
             'singleNodeTakesPlaceholder': 'Item-count/#',
             'existingNodeValue': 'Label/Red',
             'existingNodeSubValue': 'Label/UniqueValue/Red',
@@ -157,11 +158,11 @@ def test_tag_invalid_extension(self):
                                   expected_parent_tag='Item/Object/Geometric-object'),
             'twoLevels':
                 self.format_error(ValidationErrors.INVALID_PARENT_NODE, tag=0,
-                                                 index_in_tag=19, index_in_tag_end=35,
-                                                 expected_parent_tag='Item/Object/Geometric-object'),
+                                  index_in_tag=19, index_in_tag_end=35,
+                                  expected_parent_tag='Item/Object/Geometric-object'),
             'partialDuplicate':
                 self.format_error(ValidationErrors.INVALID_PARENT_NODE, tag=0,
-                                                 index_in_tag=17, index_in_tag_end=21, expected_parent_tag='Item'),
+                                  index_in_tag=17, index_in_tag_end=21, expected_parent_tag='Item'),
         }
         self.validator(test_strings, expected_results, expected_errors)
 
@@ -184,10 +185,10 @@ def test_tag_invalid(self):
             'invalidChild': self.format_error(ValidationErrors.NO_VALID_TAG_FOUND,
                                               tag=0, index_in_tag=0, index_in_tag_end=12),
             'invalidChildValidParent': self.format_error(ValidationErrors.INVALID_PARENT_NODE,
-                                            tag=0, index_in_tag=6, index_in_tag_end=11,
-                                            expected_parent_tag="Event"),
+                                                         tag=0, index_in_tag=6, index_in_tag_end=11,
+                                                         expected_parent_tag="Event"),
             'validChild': self.format_error(ValidationErrors.NO_VALID_TAG_FOUND,
-                                        tag=0, index_in_tag=0, index_in_tag_end=12),
+                                            tag=0, index_in_tag=0, index_in_tag_end=12),
         }
         self.validator(test_strings, expected_results, expected_errors)
 
diff --git a/tests/schema/test_hed_schema_group.py b/tests/schema/test_hed_schema_group.py
index 53069346c..cccf0c57c 100644
--- a/tests/schema/test_hed_schema_group.py
+++ b/tests/schema/test_hed_schema_group.py
@@ -13,10 +13,10 @@ def setUpClass(cls):
         hed_schema2 = load_schema(hed_xml, library_prefix="tl:")
         cls.hed_schema_group = HedSchemaGroup([hed_schema1, hed_schema2])
 
-    def test_schema_complicance(self):
+    def test_schema_compliance(self):
         warnings = self.hed_schema_group.check_compliance(True)
         self.assertEqual(len(warnings), 10)
 
     def test_get_tag_entry(self):
         tag_entry = self.hed_schema_group.get_tag_entry("Event", library_prefix="tl:")
-        self.assertTrue(tag_entry)
\ No newline at end of file
+        self.assertTrue(tag_entry)
diff --git a/tests/schema/test_schema_converters.py b/tests/schema/test_schema_converters.py
index f6d3ee065..8f81771d3 100644
--- a/tests/schema/test_schema_converters.py
+++ b/tests/schema/test_schema_converters.py
@@ -3,7 +3,6 @@
 import os
 
 from hed import schema
-from hed.schema.hed_schema_constants import HedSectionKey
 
 
 class TestConverterBase(unittest.TestCase):
diff --git a/tests/util/test_file_util.py b/tests/schema/test_schema_util.py
similarity index 68%
rename from tests/util/test_file_util.py
rename to tests/schema/test_schema_util.py
index ea0b3510b..fc75d4918 100644
--- a/tests/util/test_file_util.py
+++ b/tests/schema/test_schema_util.py
@@ -1,8 +1,7 @@
 import unittest
 import os
 
-import hed.util.io_util
-from hed.util import file_util
+from hed.schema.schema_io import schema_util
 
 
 class Test(unittest.TestCase):
@@ -10,12 +9,14 @@ class Test(unittest.TestCase):
     def setUpClass(cls):
         cls.default_test_url = \
             """https://raw.githubusercontent.com/hed-standard/hed-specification/master/hedxml/HED8.0.0.xml"""
-        cls.hed_xml_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), '../schema_test_data/HED8.0.0t.xml')
+        cls.hed_xml_file = os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                        '../schema_test_data/HED8.0.0t.xml')
 
     def test_url_to_file(self):
-        downloaded_file = file_util.url_to_file(self.default_test_url)
+        downloaded_file = schema_util.url_to_file(self.default_test_url)
         self.assertTrue(downloaded_file)
         os.remove(downloaded_file)
 
+
 if __name__ == '__main__':
     unittest.main()
diff --git a/tests/tools/analysis/test_analysis_util.py b/tests/tools/analysis/test_analysis_util.py
index baccd69dc..f4250b302 100644
--- a/tests/tools/analysis/test_analysis_util.py
+++ b/tests/tools/analysis/test_analysis_util.py
@@ -117,5 +117,6 @@ def test_search_tabular(self):
         self.assertEqual(len(df4.columns), 4, "search_tabular returns right number of columns when extra columns")
         self.assertEqual(len(df4.index), 155, "search_tabular has right number of rows when query okay")
 
+
 if __name__ == '__main__':
     unittest.main()
diff --git a/tests/tools/analysis/test_annotation_util.py b/tests/tools/analysis/test_annotation_util.py
index bc06ce899..ad77b6e80 100644
--- a/tests/tools/analysis/test_annotation_util.py
+++ b/tests/tools/analysis/test_annotation_util.py
@@ -211,17 +211,17 @@ def test_hed_to_df_with_definitions(self):
 
     def test_hed_to_df_to_hed(self):
         validator = HedValidator(self.hed_schema)
-        side1 = Sidecar(file=self.json_path, name="sidecar_face.json")
+        side1 = Sidecar(files=self.json_path, name="sidecar_face.json")
         issues1 = side1.validate_entries(validator, check_for_warnings=True)
         self.assertFalse(issues1, "hed_to_df_to_hed is starting with a valid JSON sidecar")
         df1 = hed_to_df(self.sidecar_face)
         self.assertIsInstance(df1, DataFrame, "hed_to_df_to_hed starting sidecar can be converted to df")
         hed2 = df_to_hed(df1, description_tag=True)
-        side2 = Sidecar(file=io.StringIO(json.dumps(hed2)), name='JSON_Sidecar2')
+        side2 = Sidecar(files=io.StringIO(json.dumps(hed2)), name='JSON_Sidecar2')
         issues2 = side2.validate_entries(validator, check_for_warnings=True)
         self.assertFalse(issues2, "hed_to_df_to_hed is valid after conversion back and forth with description True")
         hed3 = df_to_hed(df1, description_tag=False)
-        side3 = Sidecar(file=io.StringIO(json.dumps(hed3)), name='JSON_Sidecar2')
+        side3 = Sidecar(files=io.StringIO(json.dumps(hed3)), name='JSON_Sidecar2')
         issues3 = side3.validate_entries(validator, check_for_warnings=True)
         self.assertFalse(issues3, "hed_to_df_to_hed is valid after conversion back and forth with description False")
 
@@ -400,7 +400,6 @@ def test_tag_list_to_str(self):
     def test_update_cat_dict(self):
         # TODO: Improve tests
         cat_dict = self.sidecar_face['event_type']
-        value1 = cat_dict['HED']['show_face']
         self.assertNotEqual(cat_dict['HED']['show_face'], 'Blue,Red')
         _update_cat_dict(cat_dict, 'show_face', 'Blue,Red', 'n/a', description_tag=True)
         self.assertEqual(cat_dict['HED']['show_face'], 'Blue,Red')
diff --git a/tests/tools/analysis/test_definition_summary.py b/tests/tools/analysis/test_definition_summary.py
index 8ea692d0a..5acb0fc5a 100644
--- a/tests/tools/analysis/test_definition_summary.py
+++ b/tests/tools/analysis/test_definition_summary.py
@@ -12,11 +12,6 @@ def setUpClass(cls):
                                      '../../data/bids/eeg_ds003654s_hed/task-FacePerception_events.json')
 
     def test_constructor(self):
-        test_strs = ["(Face, Item-interval/#)",
-                     "Agent-action, Indeterminate-action, (Press, Keyboard-key)",
-                     f"(Definition/Famous-face-cond, (Condition-variable/Face-type, (Image, (Face, Famous)),"
-                     f"Description/A face that should be recognized by the participants))"]
-
         sidecar1 = BidsSidecarFile(self.test_json)
         self.assertEqual(sidecar1.suffix, 'events', "BidsSidecarFile should have correct name_suffix")
         self.assertEqual(sidecar1.ext, '.json', "BidsSidecarFile should have correct ext")
diff --git a/tests/tools/bids/test_bids_file_dictionary.py b/tests/tools/bids/test_bids_file_dictionary.py
index 2657e6e4d..43d95e2f4 100644
--- a/tests/tools/bids/test_bids_file_dictionary.py
+++ b/tests/tools/bids/test_bids_file_dictionary.py
@@ -44,13 +44,13 @@ def test_make_query(self):
         self.assertEqual(len(results1), len(dict1.file_dict), "make_query should return all of the entries when *.")
         results2 = dict1.make_query(query_dict={'sub': '*', 'run': ['1']})
         self.assertEqual(len(results2), 2, "make_query should return the right number of entries.")
-        results3= dict1.make_query(query_dict={'sub': '*', 'run': ['*']})
+        results3 = dict1.make_query(query_dict={'sub': '*', 'run': ['*']})
         self.assertFalse(results3, "make_query should return an empty dictionary when * is used in a list. ")
         results4 = dict1.make_query(query_dict={'sub': '*', 'run': ['*', '1']})
         self.assertEqual(len(results4), 2, "make_query should ignore the * in a list.")
         results5 = dict1.make_query(query_dict={'sub': '*', 'run': []})
         self.assertFalse(len(results5), "make_query be empty if the list for one of the entities is empty.")
-        results6 = dict1.make_query(query_dict={'sub': '*',})
+        results6 = dict1.make_query(query_dict={'sub': '*'})
         self.assertEqual(len(results6), len(dict1.file_dict), "make_query should return all of the entries when *.")
 
     def test_match_query(self):
diff --git a/tests/tools/bids/test_bids_sidecar_file.py b/tests/tools/bids/test_bids_sidecar_file.py
index 50c86e5ed..6873ebf83 100644
--- a/tests/tools/bids/test_bids_sidecar_file.py
+++ b/tests/tools/bids/test_bids_sidecar_file.py
@@ -58,70 +58,6 @@ def test_bids_sidecar_file_set_contents(self):
         sidecar1.clear_contents()
         self.assertFalse(sidecar1.contents, "BidsSidecarFile should have no contents after clearing")
 
-
-    def test_get_merged(self):
-        side_upper = BidsSidecarFile.get_merged([self.sidecar_path_upper])
-        side_lower2 = BidsSidecarFile.get_merged([self.sidecar_path_lower2])
-        side_lower3 = BidsSidecarFile.get_merged([self.sidecar_path_lower3])
-        side_merged2 = BidsSidecarFile.get_merged([self.sidecar_path_upper, self.sidecar_path_lower2])
-        self.assertIsInstance(side_upper, dict, "get_merged produces a dict when one path")
-        self.assertIsInstance(side_lower2, dict, "get_merged produces a dict when one path")
-        self.assertIsInstance(side_merged2, dict, "get_merged produces a dict when one path")
-        self.assertIn('event_type', side_upper, "get_merged upper has key event_type")
-        self.assertNotIn('event_type', side_lower2, "get_merged lower does not have event_type")
-        self.assertIn('event_type', side_merged2, "get_merged merged has key event_type from upper")
-        self.assertIn('rep_lag', side_merged2, "get_merged merged has key rep_lag from lower")
-        self.assertEqual(side_merged2['rep_lag']['HED'], side_lower2['rep_lag']['HED'],
-                         "get_merged overrode key from lower")
-
-        side_merged3 = BidsSidecarFile.get_merged([self.sidecar_path_upper, self.sidecar_path_lower3])
-        self.assertIn('face_type', side_upper, "get_merged upper has key face_type")
-        self.assertIn('face_type', side_lower3, "get_merged lower3 has key face_type")
-        self.assertIn('face_type', side_merged3, "get_merged merged3 has key face_type")
-        self.assertEqual(side_merged3['rep_lag']['HED'], side_lower3['rep_lag']['HED'],
-                         "get_merged side_merged3 got rep_lag key from lower")
-        self.assertNotEqual(side_merged3['face_type']['HED']['famous_face'],
-                            side_upper['face_type']['HED']['famous_face'],
-                            "get_merged overrode face_type key with lower has changed")
-        self.assertEqual(side_merged3['face_type']['HED']['famous_face'],
-                         side_lower3['face_type']['HED']['famous_face'],
-                         "get_merged overrode face_type key with lower3 has changed")
-
-    def test_get_merged_empty(self):
-        side_dict1 = BidsSidecarFile.get_merged([])
-        self.assertFalse(side_dict1, "get_merged is empty if empty list")
-        self.assertIsInstance(side_dict1, dict, "get_merged produces dict when empty list")
-        side_dict2 = BidsSidecarFile.get_merged(None)
-        self.assertFalse(side_dict2, "get_merged is empty if None")
-        self.assertIsInstance(side_dict2, dict, "get_merged produces dict when None")
-
-    def test_is_hed(self):
-        dict1 = {'a' : 'b', 'c': {'d': 'e'}}
-        self.assertFalse(BidsSidecarFile.is_hed(dict1), 'is_hed returns False if no HED or HED_assembled')
-        dict2 = {'HED' : 'b', 'c': {'d':'e'}}
-        self.assertTrue(BidsSidecarFile.is_hed(dict2), 'is_hed returns True if HED at top level.')
-        dict3 = {'HED_assembled': 'b', 'c': {'d':'e'}}
-        self.assertTrue(BidsSidecarFile.is_hed(dict3), 'is_hed returns True if HED_assembled at top level.')
-        dict4 = {'a' : 'b', 'c': {'d':'HED'}}
-        self.assertFalse(BidsSidecarFile.is_hed(dict4),
-                         'is_hed returns False if HED at second level is not a key')
-        dict5 = {'a' : 'b', 'c': {'d':'HED_assembled'}}
-        self.assertFalse(BidsSidecarFile.is_hed(dict5),
-                         'is_hed returns False if HED_assembled at second level is not a key')
-        dict6 = {'a' : 'b', 'c': {'HED': 'a', 'Levels': {'e':'f'}}}
-        self.assertTrue(BidsSidecarFile.is_hed(dict6), 'is_hed returns True if HED key at second level')
-        dict7 = {'a' : 'b', 'c': {'HED_assembled': 'a', 'Levels': {'e':'f'}}}
-        self.assertTrue(BidsSidecarFile.is_hed(dict7), 'is_hed returns True if HED_assembled key at second level')
-        dict8 = {'a': 'b', 'c': {'HED': {'a': 'b'}, 'Levels': {'e': 'f'}}}
-        self.assertTrue(BidsSidecarFile.is_hed(dict8), 'is_hed returns True if HED key at second level')
-        dict9 = {'a': 'b', 'c': {'HED_assembled': {'a': 'b'}, 'Levels': {'e': 'f'}}}
-        self.assertTrue(BidsSidecarFile.is_hed(dict9), 'is_hed returns True if HED_assembled key at second level')
-        dict10 = {'a': 'b', 'c': {'d': {'f': {'HED': 'g'}}}}
-        self.assertFalse(BidsSidecarFile.is_hed(dict10), 'is_hed returns False if HED key at third level')
-        dict11 = {'a': 'b', 'c': {'d': {'f': {'HED_assembled': 'g'}}}}
-        self.assertFalse(BidsSidecarFile.is_hed(dict11), 'is_hed returns False if HED_assembled key at third level')
-
-
     def test_is_sidecar_for(self):
         sidecar1 = BidsSidecarFile(self.sidecar_path)
         events1 = BidsTabularFile(self.event_path)
@@ -153,6 +89,84 @@ def test_set_contents(self):
         sidecar1.set_contents({'HED': 'xyz'})
         self.assertIs(a, sidecar1.contents, 'By default, existing contents are not overwritten.')
 
+    def test_set_contents_multiple(self):
+        bids_upper = BidsSidecarFile(self.sidecar_path_upper)
+        self.assertFalse(bids_upper.contents)
+        bids_upper.set_contents([self.sidecar_path_upper])
+
+        bids_lower2 = BidsSidecarFile(self.sidecar_path_lower2)
+        self.assertFalse(bids_lower2.contents)
+        bids_lower2.set_contents([self.sidecar_path_lower2])
+        bids_lower2_merged = BidsSidecarFile(self.sidecar_path_lower2)
+        self.assertFalse(bids_lower2_merged.contents)
+        bids_lower2_merged.set_contents([self.sidecar_path_upper, self.sidecar_path_lower2])
+
+        bids_lower3 = BidsSidecarFile(self.sidecar_path_lower3)
+        self.assertFalse(bids_lower3.contents)
+        bids_lower3.set_contents([self.sidecar_path_lower3])
+        bids_lower3_merged = BidsSidecarFile(self.sidecar_path_lower3)
+        self.assertFalse(bids_lower3_merged.contents)
+        bids_lower3_merged.set_contents([self.sidecar_path_upper, self.sidecar_path_lower3])
+
+        lower_dict2 = bids_lower2.contents.loaded_dict
+        lower_dict2_merged = bids_lower2_merged.contents.loaded_dict
+        lower_dict3 = bids_lower3.contents.loaded_dict
+        lower_dict3_merged = bids_lower3_merged.contents.loaded_dict
+        upper_dict = bids_upper.contents.loaded_dict
+        self.assertIn('event_type', upper_dict, "set_contents upper has key event_type")
+        self.assertNotIn('event_type', lower_dict2, "set_contents lower does not have event_type")
+        self.assertIn('event_type', lower_dict2_merged, "set_contents merged has key event_type from upper")
+        self.assertIn('rep_lag', lower_dict2_merged, "set_contents merged has key rep_lag from lower")
+        self.assertEqual(lower_dict2_merged['rep_lag']['HED'], lower_dict2['rep_lag']['HED'],
+                         "set_contents overrode key from lower")
+        self.assertIn('face_type', upper_dict, "set_contents upper has key face_type")
+        self.assertIn('face_type', lower_dict3, "set_contents lower3 has key face_type")
+        self.assertIn('face_type', lower_dict3_merged, "set_contents merged3 has key face_type")
+        self.assertEqual(lower_dict3_merged['rep_lag']['HED'], lower_dict3['rep_lag']['HED'],
+                         "set_contents side_merged3 got rep_lag key from lower")
+        self.assertNotEqual(lower_dict3_merged['face_type']['HED']['famous_face'],
+                            upper_dict['face_type']['HED']['famous_face'],
+                            "set_contents overrode face_type key with lower has changed")
+        self.assertEqual(lower_dict3_merged['face_type']['HED']['famous_face'],
+                         lower_dict3['face_type']['HED']['famous_face'],
+                         "set_contents overrode face_type key with lower3 has changed")
+
+    def test_set_contents_empty(self):
+        bids_upper = BidsSidecarFile(self.sidecar_path_upper)
+        self.assertFalse(bids_upper.contents, "set_contents empty when created.")
+        bids_upper.set_contents(None)
+        self.assertTrue(bids_upper.contents, "set_contents not empty when content_info is None")
+        bids_upper1 = BidsSidecarFile(self.sidecar_path_upper)
+        self.assertFalse(bids_upper1.contents, "set_contents empty when created.")
+        bids_upper1.set_contents([])
+        self.assertTrue(bids_upper.contents, "set_contents not empty when content_info is an empty list")
+
+    def test_is_hed(self):
+        dict1 = {'a': 'b', 'c': {'d': 'e'}}
+        self.assertFalse(BidsSidecarFile.is_hed(dict1), 'is_hed returns False if no HED or HED_assembled')
+        dict2 = {'HED': 'b', 'c': {'d': 'e'}}
+        self.assertTrue(BidsSidecarFile.is_hed(dict2), 'is_hed returns True if HED at top level.')
+        dict3 = {'HED_assembled': 'b', 'c': {'d': 'e'}}
+        self.assertTrue(BidsSidecarFile.is_hed(dict3), 'is_hed returns True if HED_assembled at top level.')
+        dict4 = {'a': 'b', 'c': {'d': 'HED'}}
+        self.assertFalse(BidsSidecarFile.is_hed(dict4),
+                         'is_hed returns False if HED at second level is not a key')
+        dict5 = {'a': 'b', 'c': {'d': 'HED_assembled'}}
+        self.assertFalse(BidsSidecarFile.is_hed(dict5),
+                         'is_hed returns False if HED_assembled at second level is not a key')
+        dict6 = {'a': 'b', 'c': {'HED': 'a', 'Levels': {'e': 'f'}}}
+        self.assertTrue(BidsSidecarFile.is_hed(dict6), 'is_hed returns True if HED key at second level')
+        dict7 = {'a': 'b', 'c': {'HED_assembled': 'a', 'Levels': {'e': 'f'}}}
+        self.assertTrue(BidsSidecarFile.is_hed(dict7), 'is_hed returns True if HED_assembled key at second level')
+        dict8 = {'a': 'b', 'c': {'HED': {'a': 'b'}, 'Levels': {'e': 'f'}}}
+        self.assertTrue(BidsSidecarFile.is_hed(dict8), 'is_hed returns True if HED key at second level')
+        dict9 = {'a': 'b', 'c': {'HED_assembled': {'a': 'b'}, 'Levels': {'e': 'f'}}}
+        self.assertTrue(BidsSidecarFile.is_hed(dict9), 'is_hed returns True if HED_assembled key at second level')
+        dict10 = {'a': 'b', 'c': {'d': {'f': {'HED': 'g'}}}}
+        self.assertFalse(BidsSidecarFile.is_hed(dict10), 'is_hed returns False if HED key at third level')
+        dict11 = {'a': 'b', 'c': {'d': {'f': {'HED_assembled': 'g'}}}}
+        self.assertFalse(BidsSidecarFile.is_hed(dict11), 'is_hed returns False if HED_assembled key at third level')
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/tests/tools/bids/test_bids_tabular_dictionary.py b/tests/tools/bids/test_bids_tabular_dictionary.py
index be5319437..551b35d08 100644
--- a/tests/tools/bids/test_bids_tabular_dictionary.py
+++ b/tests/tools/bids/test_bids_tabular_dictionary.py
@@ -9,9 +9,9 @@
 class Test(unittest.TestCase):
     @classmethod
     def setUpClass(cls):
-        cls.bids_base_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)),
-                                         '../../data/bids/eeg_ds003654s_hed')
-        cls.file_list = get_file_list(cls.bids_base_dir, name_suffix="_events",
+        bids_base = os.path.join(os.path.dirname(os.path.realpath(__file__)), '../../data/bids/eeg_ds003654s_hed')
+        cls.bids_base_dir = bids_base
+        cls.file_list = get_file_list(bids_base, name_suffix="_events",
                                       extensions=['.tsv'], exclude_dirs=['stimuli'])
 
     def test_bids_constructor_valid(self):
diff --git a/tests/tools/bids/test_bids_timeseries_file.py b/tests/tools/bids/test_bids_timeseries_file.py
index 03bc1276d..65c246c5c 100644
--- a/tests/tools/bids/test_bids_timeseries_file.py
+++ b/tests/tools/bids/test_bids_timeseries_file.py
@@ -1,8 +1,6 @@
 import os
 import unittest
-from hed.models import TabularInput
 from hed.tools.bids.bids_timeseries_file import BidsTimeseriesFile
-from hed.tools.bids.bids_sidecar_file import BidsSidecarFile
 
 
 class Test(unittest.TestCase):
@@ -16,21 +14,16 @@ def setUpClass(cls):
                                         '../../data/bids/eeg_ds003654s_hed/task-FacePerception_events.json')
 
     def test_constructor(self):
-        events = BidsTimeseriesFile(Test.event_path)
-        self.assertEqual(events.suffix, 'events', "BidsTabularFile should have correct events suffix")
-        self.assertEqual(events.ext, '.tsv', "BidsTabularFile should have a .tsv extension")
-        self.assertEqual(len(events.entity_dict), 3, "BidsTabularFile should have right number of entity_dict")
+        events = BidsTimeseriesFile(self.event_path)
+        self.assertEqual(events.suffix, 'events', "BidsTimeseriesFile should have correct events suffix")
+        self.assertEqual(events.ext, '.tsv', "BidsTimeseriesFile should have a .tsv extension")
+        self.assertEqual(len(events.entity_dict), 3, "BidsTimeseriesFile should have right number of entity_dict")
         events_str = str(events)
-        self.assertTrue(events_str, "BidsTabularFile should have a string representation")
+        self.assertTrue(events_str, "BidsTimeseriesFile should have a string representation")
 
     def test_set_contents(self):
-        events = BidsTimeseriesFile(Test.event_path)
-        self.assertFalse(events.contents, "BidsTabularFile should have no contents until set")
-        events.set_contents()
-        self.assertIsInstance(events.contents, TabularInput,
-                              "BidsTabularFile should have TabularInput contents after setting")
-        events.clear_contents()
-        self.assertFalse(events.contents, "BidsTabularFile should have no contents after clearing")
+        events = BidsTimeseriesFile(self.event_path)
+        self.assertFalse(events.contents, "BidsTimeseriesFile should have no contents until set")
 
 
 if __name__ == '__main__':
diff --git a/tests/util/test_io_util.py b/tests/util/test_io_util.py
index b7ab19caa..6bede1a28 100644
--- a/tests/util/test_io_util.py
+++ b/tests/util/test_io_util.py
@@ -1,8 +1,8 @@
 import os
 import unittest
 from hed.errors.exceptions import HedFileError
-from hed.util.io_util import check_filename, extract_suffix_path, generate_filename, get_dir_dictionary, get_file_list, \
-    get_path_components
+from hed.util.io_util import check_filename, extract_suffix_path, generate_filename, \
+    get_dir_dictionary, get_file_list, get_path_components
 
 from hed.util.io_util import parse_bids_filename, _split_entity
 
@@ -205,7 +205,6 @@ def test_parse_bids_filename_invalid(self):
         else:
             self.fail("parse_bids_filename should have thrown a HedFileError when missing value in name-value")
 
-
     def test_split_entity(self):
         ent_dict1 = _split_entity("apple")
         self.assertEqual("apple", ent_dict1["suffix"], "_split_entity returns the suffix of the entire piece")
@@ -221,5 +220,6 @@ def test_split_entity(self):
         self.assertTrue("bad" in ent_dict4, "_split_entity should have a bad component if invalid")
         self.assertFalse(ent_dict4["bad"], "_split_entity bad value should be empty if blank piece")
 
+
 if __name__ == '__main__':
     unittest.main()
diff --git a/tests/validator/test_hed_validator.py b/tests/validator/test_hed_validator.py
index d5b6b0137..e7f016d6d 100644
--- a/tests/validator/test_hed_validator.py
+++ b/tests/validator/test_hed_validator.py
@@ -1,7 +1,7 @@
 import unittest
 import os
 
-#from hed import
+# from hed import
 from hed.errors import ErrorContext
 from hed import schema
 from hed.models import DefMapper, HedString, SpreadsheetInput, TabularInput, Sidecar
@@ -66,7 +66,8 @@ def test_complex_file_validation_no_index(self):
                                    '../data/validator_tests/bids_events_no_index.tsv')
 
         hed_schema = schema.load_schema(schema_path)
-        json_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../data/validator_tests/bids_events.json")
+        json_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                 "../data/validator_tests/bids_events.json")
         validator = HedValidator(hed_schema=hed_schema)
         sidecar = Sidecar(json_path)
         issues = sidecar.validate_entries(validator)
@@ -85,7 +86,8 @@ def test_complex_file_validation_with_index(self):
                                    '../data/validator_tests/bids_events_with_index.tsv')
 
         hed_schema = schema.load_schema(schema_path)
-        json_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../data/validator_tests/bids_events.json")
+        json_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                 "../data/validator_tests/bids_events.json")
         validator = HedValidator(hed_schema=hed_schema)
         sidecar = Sidecar(json_path)
         issues = sidecar.validate_entries(validator)
@@ -108,12 +110,12 @@ def test_complex_file_validation_invalid(self):
                                  "../data/validator_tests/bids_events_bad_defs.json")
         validator = HedValidator(hed_schema=hed_schema)
         sidecar = Sidecar(json_path)
-        # issues = sidecar.validate_entries(hed_ops=validator, check_for_warnings=True)
-        # self.assertEqual(len(issues), 4)
+        issues = sidecar.validate_entries(hed_ops=validator, check_for_warnings=True)
+        self.assertEqual(len(issues), 4)
         input_file = TabularInput(events_path, sidecar=sidecar)
-        #
-        # validation_issues = input_file.validate_file_sidecars(validator, check_for_warnings=True)
-        # self.assertEqual(len(validation_issues), 4)
+
+        validation_issues = input_file.validate_file_sidecars(validator, check_for_warnings=True)
+        self.assertEqual(len(validation_issues), 4)
 
         validation_issues = input_file.validate_file(validator, check_for_warnings=True)
         self.assertEqual(len(validation_issues), 42)
@@ -137,7 +139,7 @@ def test_complex_file_validation_invalid_definitions_removed(self):
         self.assertEqual(len(validation_issues1), 4)
 
         validation_issues = input_file.validate_file(validator)
-        self.assertEqual(len(validation_issues), 42)
+        self.assertEqual(len(validation_issues), 21)
 
     def test_file_bad_defs_in_spreadsheet(self):
         schema_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),
@@ -208,6 +210,5 @@ def test_duplicate_group_in_definition(self):
         self.assertEqual(len(issues), 1)
 
 
-
 if __name__ == '__main__':
     unittest.main()
diff --git a/tests/validator/test_tag_validator_library.py b/tests/validator/test_tag_validator_library.py
index 2cba6da7c..53370d77f 100644
--- a/tests/validator/test_tag_validator_library.py
+++ b/tests/validator/test_tag_validator_library.py
@@ -414,6 +414,7 @@ def test_taggroup_validation(self):
         }
         self.validator_semantic(test_strings, expected_results, expected_issues, False)
 
+
 class RequiredTags(TestHed3):
     @staticmethod
     def string_obj_func(validator, check_for_warnings):