text: """UnionDict extension of dict.
  """
  
  
  __author__ = "Thomas La"
  __copyright__ = "Copyright 2007-2021, The Cogent Project"
  __credits__ = ["Gavin Huttley", "Thomas La"]
  __license__ = "BSD-3"
  __version__ = "2021.10.12a1"
  __maintainer__ = "Gavin Huttley"
  __email__ = "gavin.huttley@anu.edu.au"
  __status__ = "Production"
  
  
  class UnionDict(dict):
      """dictionary class that can be updated as a union with another dict
      entries are also accessible directly as attributes on the object"""
  
      def __init__(self, *args, **kwargs):
          """
  
          Parameters
          ----------
          data : dict or None
          """
          assert not (args and kwargs)
          if args:
              assert len(args) == 1
              kwargs = args[0]
          super().__init__(kwargs)
  
          # force all children to to UnionDicts
          for key in self:
              value = self._getsubattr_([], key)
              if not isinstance(value, dict):
                  continue
  
              if not isinstance(value, UnionDict):
                  self.update({key: UnionDict(value)})
  
      def __getattr__(self, item):
          if item in self:
              return self.get(item)
  
          try:
              return super().__getattr__(item)
          except AttributeError:
              raise AttributeError(f"'{item}' not a key or attribute")
  
      def __setattr__(self, key, value):
          if isinstance(value, dict):
              value = UnionDict(value)
  
          self.update({key: value})
  
      def __setitem__(self, key, value):
          if isinstance(value, dict):
              value = UnionDict(value)
  
          self.update({key: value})
  
      def __or__(self, other):
          result = self.__class__(self)
          result.union(other)
          return result
  
      def __ior__(self, other):
          self.union(other)
          return self
  
      def union(self, other):
          """returns the union of self with other
  
          keys unique to other are introduced, keys in self shared with other
          are updated with the value from other"""
  
          if not isinstance(other, UnionDict):
              other = UnionDict(other)
  
          for a in list(other):
  
              if self.get(a) is None or not (
                  isinstance(other.get(a), dict) and isinstance(self.get(a), dict)
              ):
                  self.update({a: other.get(a)})
                  continue
  
              path = [a]
              while len(path) > 0:
                  name = path.pop()
                  cs = self._getsubattr_(path, name)
                  cd = other._getsubattr_(path, name)
                  path.append(name)
  
                  for attr in list(cd):
                      if isinstance(cd.get(attr), dict) and isinstance(
                          cs.get(attr), dict
                      ):
                          # go into dict
                          path.append(attr)
                      else:
                          cs.update({attr: cd.get(attr)})
                  path.pop()
  
      def _getsubattr_(self, path, name):
          """returns nested values"""
          d = self
          for node in path:
              d = d.get(node)
          return d.get(name)
  
      def update(self, *args, **kwargs):
          """Converts to UnionDict."""
          assert not (args and kwargs)
          if args:
              assert len(args) == 1
              kwargs = UnionDict(args[0])
          if not isinstance(kwargs, UnionDict):
              kwargs = UnionDict(kwargs)
          super().update(kwargs)
  