# Path: invenio_records/dictutils.py
# def clear_none(d):
#     """Clear None values and empty dicts from a dict."""
#     del_keys = []
#     for k, v in d.items():
#         if v is None:
#             del_keys.append(k)
#         elif isinstance(v, dict):
#             clear_none(v)
#             if v == {}:
#                 del_keys.append(k)
#         elif isinstance(v, list):
#             clear_none_list(v)
#             if v == []:
#                 del_keys.append(k)
# 
#     # Delete the keys (cannot be done during the dict iteration)
#     for k in del_keys:
#         del d[k]
# 
# def dict_lookup(source, lookup_key, parent=False):
#     """Make a lookup into a dict based on a dot notation.
# 
#     Examples of the supported dot notation:
# 
#     - ``'a'`` - Equivalent to ``source['a']``
#     - ``'a.b'`` - Equivalent to ``source['a']['b']``
#     - ``'a.b.0'`` - Equivalent to ``source['a']['b'][0]`` (for lists)
# 
#     List notation is also supported:
# 
#     - `['a']``
#     - ``['a','b']``
#     - ``['a','b', 0]``
# 
#     :param source: The dictionary object to perform the lookup in.
#     :param parent: If parent argument is True, returns the parent node of
#                    matched object.
#     :param lookup_key: A string using dot notation, or a list of keys.
#     """
#     # Copied from dictdiffer (CERN contributed part) and slightly modified.
#     keys = parse_lookup_key(lookup_key)
# 
#     if parent:
#         keys = keys[:-1]
# 
#     # Lookup the key
#     value = source
#     for key in keys:
#         try:
#             if isinstance(value, list):
#                 key = int(key)
#             value = value[key]
#         except (TypeError, IndexError, ValueError) as exc:
#             raise KeyError(lookup_key) from exc
#     return value
# 
# Path: invenio_records/dumpers/base.py
# class Dumper:
#     """Interface for dumpers."""
# 
#     def dump(self, record, data):
#         """Dump a record that can be used a source document for Elasticsearch.
# 
#         The job of this method is to create a Python dictionary from the record
#         provided in the argument.
# 
#         If you overwrite this method without calling super, then you should
#         ensure that you make a deep copy of the record dictionary, to avoid
#         that changes to the dump affects the record.
# 
#         :param record: The record to dump.
#         :param data: The initial dump data passed in by ``record.dumps()``.
#         """
#         data.update(deepcopy(dict(record)))
#         return data
# 
#     def load(self, data, record_cls):
#         """Load a record from the source document of an Elasticsearch hit.
# 
#         The job of this method, is to create a record of type ``record_cls``
#         based on the input ``data``.
# 
#         :param data: A Python dictionary representing the data to load.
#         :param records_cls: The record class to be constructed.
#         :returns: A instance of ``record_cls``.
#         """
#         raise NotImplementedError()
# 
# Path: invenio_records/errors.py
# class MissingModelError(RecordsError):
#     """Error raised when a record has no model."""
# 
# Path: invenio_records/models.py
# class RecordMetadata(db.Model, RecordMetadataBase):
#     """Represent a record metadata."""
# 
#     __tablename__ = 'records_metadata'
# 
#     # Enables SQLAlchemy-Continuum versioning
#     __versioned__ = {}
# 
# Path: invenio_records/signals.py



import inspect
import warnings
from copy import deepcopy
from flask import current_app
from invenio_db import db
from jsonpatch import apply_patch
from sqlalchemy.orm.attributes import flag_modified
from sqlalchemy.orm.exc import NoResultFound
from sqlalchemy_continuum.utils import parent_class
from werkzeug.local import LocalProxy
from .dictutils import clear_none, dict_lookup
from .dumpers import Dumper
from .errors import MissingModelError
from .models import RecordMetadata
from .signals import after_record_delete, after_record_insert, \
    after_record_revert, after_record_update, before_record_delete, \
    before_record_insert, before_record_revert, before_record_update
                before_record_delete.send(
                    current_app._get_current_object(),
                    record=self
                )

            # Run pre delete extensions
            for e in self._extensions:
                e.pre_delete(self, force=force)

            if force:
                db.session.delete(self.model)
            else:
                self.model.is_deleted = True
                db.session.merge(self.model)

        if self.send_signals:
            after_record_delete.send(
                current_app._get_current_object(),
                record=self
            )

        # Run post delete extensions
        for e in self._extensions:
            e.post_delete(self, force=force)

        return self

    def undelete(self):
        """Undelete a soft-deleted record."""
        if self.model is None:

# Path: invenio_records/dictutils.py
# def clear_none(d):
#     """Clear None values and empty dicts from a dict."""
#     del_keys = []
#     for k, v in d.items():
#         if v is None:
#             del_keys.append(k)
#         elif isinstance(v, dict):
#             clear_none(v)
#             if v == {}:
#                 del_keys.append(k)
#         elif isinstance(v, list):
#             clear_none_list(v)
#             if v == []:
#                 del_keys.append(k)
# 
#     # Delete the keys (cannot be done during the dict iteration)
#     for k in del_keys:
#         del d[k]
# 
# def dict_lookup(source, lookup_key, parent=False):
#     """Make a lookup into a dict based on a dot notation.
# 
#     Examples of the supported dot notation:
# 
#     - ``'a'`` - Equivalent to ``source['a']``
#     - ``'a.b'`` - Equivalent to ``source['a']['b']``
#     - ``'a.b.0'`` - Equivalent to ``source['a']['b'][0]`` (for lists)
# 
#     List notation is also supported:
# 
#     - `['a']``
#     - ``['a','b']``
#     - ``['a','b', 0]``
# 
#     :param source: The dictionary object to perform the lookup in.
#     :param parent: If parent argument is True, returns the parent node of
#                    matched object.
#     :param lookup_key: A string using dot notation, or a list of keys.
#     """
#     # Copied from dictdiffer (CERN contributed part) and slightly modified.
#     keys = parse_lookup_key(lookup_key)
# 
#     if parent:
#         keys = keys[:-1]
# 
#     # Lookup the key
#     value = source
#     for key in keys:
#         try:
#             if isinstance(value, list):
#                 key = int(key)
#             value = value[key]
#         except (TypeError, IndexError, ValueError) as exc:
#             raise KeyError(lookup_key) from exc
#     return value
# 
# Path: invenio_records/dumpers/base.py
# class Dumper:
#     """Interface for dumpers."""
# 
#     def dump(self, record, data):
#         """Dump a record that can be used a source document for Elasticsearch.
# 
#         The job of this method is to create a Python dictionary from the record
#         provided in the argument.
# 
#         If you overwrite this method without calling super, then you should
#         ensure that you make a deep copy of the record dictionary, to avoid
#         that changes to the dump affects the record.
# 
#         :param record: The record to dump.
#         :param data: The initial dump data passed in by ``record.dumps()``.
#         """
#         data.update(deepcopy(dict(record)))
#         return data
# 
#     def load(self, data, record_cls):
#         """Load a record from the source document of an Elasticsearch hit.
# 
#         The job of this method, is to create a record of type ``record_cls``
#         based on the input ``data``.
# 
#         :param data: A Python dictionary representing the data to load.
#         :param records_cls: The record class to be constructed.
#         :returns: A instance of ``record_cls``.
#         """
#         raise NotImplementedError()
# 
# Path: invenio_records/errors.py
# class MissingModelError(RecordsError):
#     """Error raised when a record has no model."""
# 
# Path: invenio_records/models.py
# class RecordMetadata(db.Model, RecordMetadataBase):
#     """Represent a record metadata."""
# 
#     __tablename__ = 'records_metadata'
# 
#     # Enables SQLAlchemy-Continuum versioning
#     __versioned__ = {}
# 
# Path: invenio_records/signals.py



# Path: invenio_records/api.py
import inspect
import warnings
from copy import deepcopy
from flask import current_app
from invenio_db import db
from jsonpatch import apply_patch
from sqlalchemy.orm.attributes import flag_modified
from sqlalchemy.orm.exc import NoResultFound
from sqlalchemy_continuum.utils import parent_class
from werkzeug.local import LocalProxy
from .dictutils import clear_none, dict_lookup
from .dumpers import Dumper
from .errors import MissingModelError
from .models import RecordMetadata
from .signals import after_record_delete, after_record_insert, \
    after_record_revert, after_record_update, before_record_delete, \
    before_record_insert, before_record_revert, before_record_update

                before_record_delete.send(
                    current_app._get_current_object(),
                    record=self
                )

            # Run pre delete extensions
            for e in self._extensions:
                e.pre_delete(self, force=force)

            if force:
                db.session.delete(self.model)
            else:
                self.model.is_deleted = True
                db.session.merge(self.model)

        if self.send_signals:
            after_record_delete.send(
                current_app._get_current_object(),
                record=self
            )

        # Run post delete extensions
        for e in self._extensions:
            e.post_delete(self, force=force)

        return self

    def undelete(self):
        """Undelete a soft-deleted record."""
        if self.model is None:

            raise MissingModelError()