text: from __future__ import (absolute_import, division, print_function,
                          unicode_literals)
  
  import six
  from jsonschema import validate as js_validate
  import warnings
  import uuid
  import time as ttime
  import pandas as pd
  from ..utils import sanitize_np, apply_to_dict_recursively
  
  class DatumNotFound(Exception):
      pass
  
  
  def doc_or_uid_to_uid(doc_or_uid):
      """Given Document or uid return the uid
  
      Parameters
      ----------
      doc_or_uid : dict or str
          If str, then assume uid and pass through, if not, return
          the 'uid' field
  
      Returns
      -------
      uid : str
          A string version of the uid of the given document
  
      """
      if not isinstance(doc_or_uid, six.string_types):
          doc_or_uid = doc_or_uid['uid']
      return doc_or_uid
  
  
  def _get_datum_from_datum_id(col, datum_id, datum_cache, logger):
      try:
          datum = datum_cache[datum_id]
      except KeyError:
          # find the current document
          edoc = col.find_one({'datum_id': datum_id})
          if edoc is None:
              raise DatumNotFound(
                  "No datum found with datum_id {!r}".format(datum_id))
          # save it for later
          datum = dict(edoc)
  
          res = edoc['resource']
          count = 0
          for dd in col.find({'resource': res}):
              count += 1
              d_id = dd['datum_id']
              if d_id not in datum_cache:
                  datum_cache[d_id] = dict(dd)
          if count > datum_cache.max_size:
              logger.warn("More datum in a resource than your "
                          "datum cache can hold.")
  
      datum.pop('_id', None)
      return datum
  
  
  def retrieve(col, datum_id, datum_cache, get_spec_handler, logger):
      datum = _get_datum_from_datum_id(col, datum_id, datum_cache, logger)
      handler = get_spec_handler(datum['resource'])
      return handler(**datum['datum_kwargs'])
  
  
  def resource_given_datum_id(col, datum_id, datum_cache, logger):
      datum_id = doc_or_uid_to_uid(datum_id)
      datum = _get_datum_from_datum_id(col, datum_id, datum_cache, logger)
      res = datum['resource']
      return res
  
  
  def resource_given_uid(col, resource):
      uid = doc_or_uid_to_uid(resource)
      ret = col.find_one({'uid': uid})
      ret.pop('_id', None)
      ret['id'] = ret['uid']
      return ret
  
  
  def bulk_insert_datum(col, resource, datum_ids,
                        datum_kwarg_list):
  
      resource_id = doc_or_uid_to_uid(resource)
  
      def datum_factory():
          for d_id, d_kwargs in zip(datum_ids, datum_kwarg_list):
              datum = dict(resource=resource_id,
                           datum_id=str(d_id),
                           datum_kwargs=dict(d_kwargs))
              apply_to_dict_recursively(datum, sanitize_np)
              yield datum
  
      col.insert(datum_factory())
  
  
  def bulk_register_datum_table(datum_col,
                                resource_uid,
                                dkwargs_table,
                                validate):
      if validate:
          raise
  
      d_ids = [str(uuid.uuid4()) for j in range(len(dkwargs_table))]
      dkwargs_table = pd.DataFrame(dkwargs_table)
      bulk_insert_datum(datum_col, resource_uid, d_ids, [
          dict(r) for _, r in dkwargs_table.iterrows()])
      return d_ids
  
  
  def register_datum(col, resource_uid, datum_kwargs):
      datum_uid = str(uuid.uuid4())
      datum = insert_datum(col, resource_uid, datum_uid, datum_kwargs, {}, None)
      return datum['datum_id']
  
  
  def insert_datum(col, resource, datum_id, datum_kwargs, known_spec,
                   resource_col, ignore_duplicate_error=False,
                   duplicate_exc=None):
      if ignore_duplicate_error:
          assert duplicate_exc is not None
  
      if duplicate_exc is None:
          class _PrivateException(Exception):
              pass
          duplicate_exc = _PrivateException
      try:
          resource['spec']
          spec = resource['spec']
  
          if spec in known_spec:
              js_validate(datum_kwargs, known_spec[spec]['datum'])
      except (AttributeError, TypeError):
          pass
  
      resource_uid = doc_or_uid_to_uid(resource)
  
      datum = dict(resource=resource_uid,
                   datum_id=str(datum_id),
                   datum_kwargs=dict(datum_kwargs))
      apply_to_dict_recursively(datum, sanitize_np)
      # We are transitioning from ophyd objects inserting directly into a
      # Registry to ophyd objects passing documents to the RunEngine which in
      # turn inserts them into a Registry. During the transition period, we allow
      # an ophyd object to attempt BOTH so that configuration files are
      # compatible with both the new model and the old model. Thus, we need to
      # ignore the second attempt to insert.
      try:
          col.insert_one(datum)
      except duplicate_exc:
          if ignore_duplicate_error:
              warnings.warn("Ignoring attempt to insert Resource with duplicate "
                            "uid, assuming that both ophyd and bluesky "
                            "attempted to insert this document. Remove the "
                            "Registry (`reg` parameter) from your ophyd "
                            "instance to remove this warning.")
          else:
              raise
      # do not leak mongo objectID
      datum.pop('_id', None)
  
      return datum
  
  
  def insert_resource(col, spec, resource_path, resource_kwargs,
                      known_spec, root, path_semantics='posix', uid=None,
                      run_start=None,
                      ignore_duplicate_error=False, duplicate_exc=None):
      if ignore_duplicate_error:
          assert duplicate_exc is not None
      if duplicate_exc is None:
          class _PrivateException(Exception):
              pass
          duplicate_exc = _PrivateException
      resource_kwargs = dict(resource_kwargs)
      if spec in known_spec:
          js_validate(resource_kwargs, known_spec[spec]['resource'])
      if uid is None:
          uid = str(uuid.uuid4())
  
      resource_object = dict(spec=str(spec),
                             resource_path=str(resource_path),
                             root=str(root),
                             resource_kwargs=resource_kwargs,
                             path_semantics=path_semantics,
                             uid=uid)
      # This is special-cased because it was added later.
      # Someday this may be required and no longer special-cased.
      if run_start is not None:
          resource_object['run_start'] = run_start
      # We are transitioning from ophyd objects inserting directly into a
      # Registry to ophyd objects passing documents to the RunEngine which in
      # turn inserts them into a Registry. During the transition period, we allow
      # an ophyd object to attempt BOTH so that configuration files are
      # compatible with both the new model and the old model. Thus, we need to
      # ignore the second attempt to insert.
      try:
          col.insert_one(resource_object)
      except duplicate_exc:
          if ignore_duplicate_error:
              warnings.warn("Ignoring attempt to insert Datum with duplicate "
                            "datum_id, assuming that both ophyd and bluesky "
                            "attempted to insert this document. Remove the "
                            "Registry (`reg` parameter) from your ophyd "
                            "instance to remove this warning.")
          else:
              raise
      resource_object['id'] = resource_object['uid']
      resource_object.pop('_id', None)
      return resource_object
  
  
  def update_resource(update_col, resource_col, old, new, cmd, cmd_kwargs):
      '''Update a resource document
  
      Parameters
      ----------
      update_col : Collection
          The collection to record audit trail in
      resource_col : Collection
          The resource collection
  
      old : dict
          The old resource document
  
      new : dict
          The new resource document
  
      cmd : str
          The name of the operation which generated this update
  
      cmd_kwargs : dict
          The arguments that went into the update (excluding the resource id)
  
  
      Returns
      -------
      ret : dict
          The new resource document
  
      log_object : dict
          The history object inserted (with oid removed)
      '''
      if old['uid'] != new['uid']:
          raise RuntimeError('must not change the resource uid')
      uid = old['uid']
      log_object = {'resource': uid,
                    'old': old,
                    'new': new,
                    'time': ttime.time(),
                    'cmd': cmd,
                    'cmd_kwargs': cmd_kwargs}
  
      update_col.insert_one(log_object)
      result = resource_col.replace_one({'uid': uid}, new)
      ret = resource_given_uid(resource_col, uid)
      # TODO look inside of result
      del result
      log_object.pop('_id', None)
      return ret, log_object
  
  
  def get_resource_history(col, resource):
      uid = doc_or_uid_to_uid(resource)
      cursor = col.find({'resource': uid})
      for doc in cursor:
          for k in ['new', 'old']:
              d = doc[k]
              d.pop('_id', None)
              d['id'] = d['uid']
              doc[k] = d
          doc.pop('_id', None)
          yield doc
  
  
  def get_datumkw_by_resuid_gen(datum_col, resource_uid):
      '''Given a resource uid, get all datum_kwargs
  
      No order is guaranteed.
  
      Internally the result of this is passed to the `get_file_list` method
      of the handler object in `change_root`
  
      Parameters
      ----------
      datum_col : Collection
          The Datum collection
  
      resource_uid : dict or str
         The resource to work on
  
      Yields
      ------
      datum_kwarg : dict
      '''
      resource_uid = doc_or_uid_to_uid(resource_uid)
      cur = datum_col.find({'resource': resource_uid})
  
      for d in cur:
          yield d['datum_kwargs']
  
  
  def get_datum_by_res_gen(datum_col, resource_uid):
      '''Given a resource uid, get all datums
  
      No order is guaranteed.
  
      Internally the result of this is passed to the `get_file_list` method
      of the handler object in `change_root`
  
      Parameters
      ----------
      datum_col : Collection
          The Datum collection
  
      resource_uid : dict or str
         The resource to work on
  
      Yields
      ------
      datum : dict
      '''
      resource_uid = doc_or_uid_to_uid(resource_uid)
      cur = datum_col.find({'resource': resource_uid})
  
      for d in cur:
          yield d
  
  
  def get_file_list(resource, datum_kwarg_gen, get_spec_handler):
      """
      Given a resource and an iterable of datum kwargs, get a list of
      associated files.
  
      DO NOT USE FOR COPYING OR MOVING. This is for debugging only.
      See the methods for moving and copying on the Registry object.
      """
      handler = get_spec_handler(resource['uid'])
      return handler.get_file_list(datum_kwarg_gen)
  