text: """Authors: Cody Baker and Ben Dichter."""
  import json
  from jsonschema import validate
  from typing import Optional, Dict
  from pathlib import Path
  
  from pynwb import NWBFile
  from pynwb.file import Subject
  
  from .tools.nwb_helpers import get_default_nwbfile_metadata, make_nwbfile_from_metadata, make_or_load_nwbfile
  from .utils import (
      get_schema_from_hdmf_class,
      get_schema_for_NWBFile,
      dict_deep_update,
      get_base_schema,
      unroot_schema,
      fill_defaults,
  )
  
  from .utils.json_schema import NWBMetaDataEncoder
  
  
  class NWBConverter:
      """Primary class for all NWB conversion classes."""
  
      data_interface_classes = None
  
      @classmethod
      def get_source_schema(cls):
          """Compile input schemas from each of the data interface classes."""
          source_schema = get_base_schema(
              root=True,
              id_="source.schema.json",
              title="Source data schema",
              description="Schema for the source data, files and directories",
              version="0.1.0",
          )
          for interface_name, data_interface in cls.data_interface_classes.items():
              source_schema["properties"].update({interface_name: unroot_schema(data_interface.get_source_schema())})
          return source_schema
  
      @classmethod
      def get_conversion_options_schema(cls):
          """Compile conversion option schemas from each of the data interface classes."""
          conversion_options_schema = get_base_schema(
              root=True,
              id_="conversion_options.schema.json",
              title="Conversion options schema",
              description="Schema for the conversion options",
              version="0.1.0",
          )
          for interface_name, data_interface in cls.data_interface_classes.items():
              conversion_options_schema["properties"].update(
                  {interface_name: unroot_schema(data_interface.get_conversion_options_schema())}
              )
          return conversion_options_schema
  
      @classmethod
      def validate_source(cls, source_data: Dict[str, dict], verbose: bool = True):
          """Validate source_data against Converter source_schema."""
          cls._validate_source_data(source_data=source_data, verbose=verbose)
  
      def __init__(self, source_data: Dict[str, dict], verbose: bool = True):
          """Validate source_data against source_schema and initialize all data interfaces."""
          self.verbose = verbose
          self._validate_source_data(source_data=source_data, verbose=self.verbose)
          self.data_interface_objects = {
              name: data_interface(**source_data[name])
              for name, data_interface in self.data_interface_classes.items()
              if name in source_data
          }
  
      def get_metadata_schema(self):
          """Compile metadata schemas from each of the data interface objects."""
          metadata_schema = get_base_schema(
              id_="metadata.schema.json",
              root=True,
              title="Metadata",
              description="Schema for the metadata",
              version="0.1.0",
              required=["NWBFile"],
              properties=dict(NWBFile=get_schema_for_NWBFile(), Subject=get_schema_from_hdmf_class(Subject)),
          )
          for data_interface in self.data_interface_objects.values():
              interface_schema = unroot_schema(data_interface.get_metadata_schema())
              metadata_schema = dict_deep_update(metadata_schema, interface_schema)
  
          default_values = self.get_metadata()
          fill_defaults(metadata_schema, default_values)
          return metadata_schema
  
      def get_metadata(self):
          """Auto-fill as much of the metadata as possible. Must comply with metadata schema."""
          metadata = get_default_nwbfile_metadata()
          for interface in self.data_interface_objects.values():
              interface_metadata = interface.get_metadata()
              metadata = dict_deep_update(metadata, interface_metadata)
          return metadata
  
      def get_conversion_options(self):
          """Auto-fill as much of the conversion options as possible. Must comply with conversion_options_schema."""
          conversion_options = dict()
          for interface_name, interface in self.data_interface_objects.items():
              conversion_options[interface_name] = interface.get_conversion_options()
          return conversion_options
  
      def validate_metadata(self, metadata: Dict[str, dict]):
          """Validate metadata against Converter metadata_schema."""
          encoder = NWBMetaDataEncoder()
          # The encoder produces a serialiazed object so we de serialized it for comparison
          serialied_metadata = encoder.encode(metadata)
          decoded_metadata = json.loads(serialied_metadata)
          validate(instance=decoded_metadata, schema=self.get_metadata_schema())
          if self.verbose:
              print("Metadata is valid!")
  
      def validate_conversion_options(self, conversion_options: Dict[str, dict]):
          """Validate conversion_options against Converter conversion_options_schema."""
          validate(instance=conversion_options, schema=self.get_conversion_options_schema())
          if self.verbose:
              print("conversion_options is valid!")
  
      def _validate_source_data(self, source_data: Dict[str, dict], verbose: bool = True):
          validate(instance=source_data, schema=self.get_source_schema())
          if verbose:
              print("Source data is valid!")
  
      def run_conversion(
          self,
          nwbfile_path: Optional[str] = None,
          nwbfile: Optional[NWBFile] = None,
          metadata: Optional[dict] = None,
          overwrite: bool = False,
          conversion_options: Optional[dict] = None,
      ) -> NWBFile:
          """
          Run the NWB conversion over all the instantiated data interfaces.
  
          Parameters
          ----------
          nwbfile_path: FilePathType
              Path for where to write or load (if overwrite=False) the NWBFile.
              If specified, the context will always write to this location.
          nwbfile: NWBFile, optional
              An in-memory NWBFile object to write to the location.
          metadata: dict, optional
              Metadata dictionary with information used to create the NWBFile when one does not exist or overwrite=True.
          overwrite: bool, optional
              Whether or not to overwrite the NWBFile if one exists at the nwbfile_path.
              The default is False (append mode).
          verbose: bool, optional
              If 'nwbfile_path' is specified, informs user after a successful write operation.
              The default is True.
          conversion_options: dict, optional
              Similar to source_data, a dictionary containing keywords for each interface for which non-default
              conversion specification is requested.
  
          Returns
          -------
          nwbfile: NWBFile
              The in-memory NWBFile object after all conversion operations are complete.
          """
          if metadata is None:
              metadata = self.get_metadata()
          self.validate_metadata(metadata=metadata)
  
          if conversion_options is None:
              conversion_options = dict()
          default_conversion_options = self.get_conversion_options()
          conversion_options_to_run = dict_deep_update(default_conversion_options, conversion_options)
          self.validate_conversion_options(conversion_options=conversion_options_to_run)
  
          with make_or_load_nwbfile(
              nwbfile_path=nwbfile_path,
              nwbfile=nwbfile,
              metadata=metadata,
              overwrite=overwrite,
              verbose=self.verbose,
          ) as nwbfile_out:
              for interface_name, data_interface in self.data_interface_objects.items():
                  data_interface.run_conversion(
                      nwbfile=nwbfile_out, metadata=metadata, **conversion_options_to_run.get(interface_name, dict())
                  )
          return nwbfile_out
  