Repository: HTTP-APIs/hydrus (python)

Issue:
Add support for datetime column in hydrus
### I'm submitting a 
- [ ] bug report.
- [x] feature request.

### Current Behaviour:
<!-- Describe about the bug -->
Presently, we can create columns in database for only integer, float, string datatypes in database. 
### Expected Behaviour:
<!-- Describe what will happen if bug is removed -->
A user should be able to specify datatype like `"xsd:dateTime"` while creating API doc. Database columns should be created accordingly. Ref  : https://www.w3.org/TR/xmlschema-2/#dateTime

### Steps to reproduce:
<!-- If you can then please provide the steps to reproduce the bug -->

### Snapshot:
<!-- If you can then please provide the screenshot of the issue you are facing -->

### Environment:
<!-- Please provide the following environment details --> 
* python version
* pip version
* OS details

### Do you want to work on this issue?
<!-- yes/no -->
Yes

Pull request description:
Added support for datetime column in datebase created by hydrus
<!-- Please create (if there is not one yet) a issue before sending a PR -->
<!-- Add issue number (Eg: fixes #123) -->
<!-- Always provide changes in existing tests or new tests -->

Fixes #598 

### Checklist
- [x] My branch is up-to-date with upstream/develop branch.
- [x] Everything works and tested for Python 3.6.0 and above.

### Current behaviour
<!-- Describe the code you are going to change and its behaviour -->
Previously, we can had support for only integer, float, string datatypes in database. I've added support for a Datetime column.

### New expected behaviour
<!-- Describe the new code and its expected behaviour -->

- SQLAlchemy Datetime Column only takes a datetime object. But a user cannot send a datetime object in request body ( not json serializable ). This is why I created helper functions to convert datetime string to a python datetime object and then it'll be inserted to database.
- The helper function (`get_modified_object`)  checks if there is any supported property in object (request body) which is actually a datetime field. And then returns a modified object accordingly.
- The helper function needs a doc to check which supported_properties are actually datetime fields in an object. The only problem was crud tests were failing because crud.insert is called directly in these tests. Couldn't call `get_doc()` directly in `crud.insert_object` So I had to pass the doc as an argument in `crud.insert` and `crud.insert_object` functions as well. 
- Everything is tested, tests are passing. Also to test that columns are created with correct datatype, I've made required changes in _conftest.py_'s `gen_dummy_object` function.
-  Added datatype for Prop1 and Prop2 supported properties in doc_writer_sample in hydrus. ( It is used for testing )
### Change logs 
- db_models.py
- resource_based_classes.py
- item_collection_helpers.py
- itemhelpers.py
- items_helpers.py
- crud.py
- conftest.py
- test_crud.py
- doc_writer_sample.py
<!-- #### Added -->
<!-- Edit these points below to describe the new features added with this PR -->
<!-- - Feature 1 -->
<!-- - Feature 2 -->


<!-- #### Added -->
<!-- Edit these points below to describe the new features added with this PR -->
<!-- - Feature 1 -->
<!-- - Feature 2 -->


<!-- #### Changed -->
<!-- Edit these points below to describe the changes made in existing functionality with this PR -->
<!-- - Change 1 -->
<!-- - Change 1 -->


<!-- #### Fixed -->
<!-- Edit these points below to describe the bug fixes made with this PR -->
<!-- - Bug 1 -->


<!-- #### Removed -->
<!-- Edit these points below to describe the removed features with this PR -->
<!-- - Deprecated feature 1 -->

<!-- #### Changed -->
<!-- Edit these points below to describe the changes made in existing functionality with this PR -->
<!-- - Change 1 -->
<!-- - Change 1 -->


<!-- #### Fixed -->
<!-- Edit these points below to describe the bug fixes made with this PR -->
<!-- - Bug 1 -->


<!-- #### Removed -->
<!-- Edit these points below to describe the removed features with this PR -->
<!-- - Deprecated feature 1 -->


Patch:
diff --git a/hydrus/data/crud.py b/hydrus/data/crud.py
index f9d8ccc..b8902ca 100644
--- a/hydrus/data/crud.py
+++ b/hydrus/data/crud.py
@@ -49,6 +49,7 @@ from hydrus.data.crud_helpers import (
 
 # from sqlalchemy.orm.session import Session
 from sqlalchemy.orm.scoping import scoped_session
+from hydra_python_core.doc_writer import HydraDoc
 from typing import Dict, Optional, Any, List
 
 from hydrus.data.resource_based_classes import (
@@ -97,12 +98,14 @@ def get(
 
 
 def insert(
+    doc_: HydraDoc,
     object_: Dict[str, Any],
     session: scoped_session,
     id_: Optional[str] = None,
     collection: bool = False,
 ) -> str:
     """Insert an object to database [POST] and returns the inserted object.
+    :param doc_ : Hydra Doc object
     :param object_: object to be inserted
     :param session: sqlalchemy scoped session
     :param id_: id of the object to be inserted (optional param)
@@ -124,15 +127,19 @@ def insert(
     object_template = copy.deepcopy(object_)
     if id_ is not None:
         object_template["id"] = id_
-    inserted_object_id = insert_object(object_template, session, collection)
+    inserted_object_id = insert_object(doc_, object_template, session, collection)
     return inserted_object_id
 
 
 def insert_multiple(
-    objects_: List[Dict[str, Any]], session: scoped_session, id_: Optional[str] = ""
+    doc: HydraDoc,
+    objects_: List[Dict[str, Any]],
+    session: scoped_session,
+    id_: Optional[str] = ""
 ) -> List[str]:
     """
     Adds a list of object with given ids to the database
+    :param doc : Hydra Doc object
     :param objects_: List of dict's to be added to the database
     :param session: scoped session from getSession in utils
     :param id_: optional parameter containing the ids of objects that have to be inserted
@@ -167,7 +174,7 @@ def insert_multiple(
             pass
         except TypeError:
             pass
-        inserted_object_id = insert(object_, session, id_of_object_)
+        inserted_object_id = insert(doc, object_, session, id_of_object_)
         instance_id_list.append(inserted_object_id)
 
     return instance_id_list
@@ -209,6 +216,7 @@ def delete_multiple(id_: List[int], type_: str, session: scoped_session) -> None
 
 
 def update(
+    doc_: HydraDoc,
     id_: str,
     type_: str,
     object_: Dict[str, str],
@@ -218,6 +226,7 @@ def update(
     collection: bool = False,
 ) -> str:
     """Update an object properties based on the given object [PUT].
+    :param doc_ : Hydra Doc object
     :param id_: if of object to be updated
     :param type_: type of object to be updated
     :param object_: object that has to be inserted
@@ -228,7 +237,7 @@ def update(
     :return: id of updated object
     """
     query_info = {"@type": type_, "id_": id_}
-    updated_object_id = update_object(object_, query_info, session, collection)
+    updated_object_id = update_object(doc_, object_, query_info, session, collection)
     return updated_object_id
 
 
diff --git a/hydrus/data/db_models.py b/hydrus/data/db_models.py
index 45f3ef3..b2fefb8 100644
--- a/hydrus/data/db_models.py
+++ b/hydrus/data/db_models.py
@@ -101,7 +101,11 @@ class Resource:
                     foreign_table_name, title
                 )
             else:
-                datatype_keys = {'integer': Integer, 'float': Float, 'decimal': Float}
+                datatype_keys = {'integer': Integer,
+                                 'float': Float,
+                                 'decimal': Float,
+                                 'string': String,
+                                 'dateTime': DateTime}
                 if 'range' in supported_property:
                     datatype = supported_property['range'].split('#')[1]
                     if datatype in datatype_keys:
diff --git a/hydrus/data/exceptions.py b/hydrus/data/exceptions.py
index 1335c85..73a8712 100644
--- a/hydrus/data/exceptions.py
+++ b/hydrus/data/exceptions.py
@@ -195,3 +195,17 @@ class PropertyNotGiven(Exception):
         """Return the HTTP response for the Exception."""
         description = f"The property {self.type_} is not given."
         return HydraError(code=400, title="Property not given", desc=description)
+
+
+class InvalidDateTimeFormat(Exception):
+    """Error when a datetime field input is invalid"""
+
+    def __init__(self, field_: str) -> None:
+        """Constructor."""
+        self.field_ = field_
+
+    def get_HTTP(self) -> HydraError:
+        """Return the HTTP response for the Exception."""
+        description = (f"The format of {self.field_} is invalid."
+                       f" Datetime input should be in ISO format.")
+        return HydraError(code=400, title="Invalid Datetime format", desc=description)
diff --git a/hydrus/data/helpers/item_collection_helpers.py b/hydrus/data/helpers/item_collection_helpers.py
index e4b3ae4..4462735 100644
--- a/hydrus/data/helpers/item_collection_helpers.py
+++ b/hydrus/data/helpers/item_collection_helpers.py
@@ -15,6 +15,7 @@ from hydrus.data.exceptions import (
     InvalidSearchParameter,
     OffsetOutOfRange,
     PropertyNotGiven,
+    InvalidDateTimeFormat
 )
 
 from hydrus.data.helpers import (
@@ -34,6 +35,7 @@ from hydrus.utils import (
     get_hydrus_server_url,
     get_page_size,
     get_pagination,
+    get_doc
 )
 
 
@@ -99,6 +101,7 @@ def item_collection_put_response(path: str) -> Response:
     :rtype: Response
     """
     object_ = json.loads(request.data.decode("utf-8"))
+    doc_object = get_doc()
     collections, parsed_classes = get_collections_and_parsed_classes()
     is_collection = False
     if path in parsed_classes:
@@ -118,7 +121,7 @@ def item_collection_put_response(path: str) -> Response:
         try:
             # Insert object and return location in Header
             object_id = crud.insert(
-                object_=object_, session=get_session(), collection=is_collection
+                object_=object_, session=get_session(), doc_=doc_object, collection=is_collection
             )
             resource_url = (
                 f"{get_hydrus_server_url()}{get_api_name()}/{path}/{object_id}"
@@ -133,7 +136,8 @@ def item_collection_put_response(path: str) -> Response:
             return set_response_headers(
                 jsonify(status_response), headers=headers_, status_code=status.code
             )
-        except (ClassNotFound, InstanceExists, PropertyNotFound, PropertyNotGiven) as e:
+        except (ClassNotFound, InstanceExists, PropertyNotFound,
+                PropertyNotGiven, InvalidDateTimeFormat) as e:
             error = e.get_HTTP()
             return error_response(error)
     else:
diff --git a/hydrus/data/helpers/itemhelpers.py b/hydrus/data/helpers/itemhelpers.py
index 99ae7e9..c8b3e4a 100644
--- a/hydrus/data/helpers/itemhelpers.py
+++ b/hydrus/data/helpers/itemhelpers.py
@@ -9,6 +9,7 @@ from hydrus.data.exceptions import (
     ClassNotFound,
     InstanceNotFound,
     InstanceExists,
+    InvalidDateTimeFormat,
     PropertyNotFound,
     MemberInstanceNotFound,
 )
@@ -24,7 +25,7 @@ from hydrus.data.helpers import (
     parse_collection_members,
     get_collections_and_parsed_classes,
 )
-from hydrus.utils import get_session, get_api_name, get_hydrus_server_url
+from hydrus.utils import get_session, get_api_name, get_hydrus_server_url, get_doc
 from hydrus.extensions.socketio_factory import socketio
 
 
@@ -52,6 +53,7 @@ def items_get_check_support(id_, class_type, class_path, path, is_collection=Fal
 def items_post_check_support(id_, object_, class_path, path, is_collection):
     """Check if class_type supports POST operation"""
     collections, parsed_classes = get_collections_and_parsed_classes()
+    doc = get_doc()
     if path in parsed_classes:
         class_path = path
         obj_type = getType(path, "PUT")
@@ -68,6 +70,7 @@ def items_post_check_support(id_, object_, class_path, path, is_collection):
             # Update the right ID if the object is valid and matches
             # type of Item
             object_id = crud.update(
+                doc,
                 object_=object_,
                 id_=id_,
                 type_=object_["@type"],
@@ -99,7 +102,8 @@ def items_post_check_support(id_, object_, class_path, path, is_collection):
             status_response["iri"] = resource_url
             return set_response_headers(jsonify(status_response), headers=headers_)
 
-        except (ClassNotFound, InstanceNotFound, InstanceExists, PropertyNotFound) as e:
+        except (ClassNotFound, InstanceNotFound, InstanceExists,
+                PropertyNotFound, InvalidDateTimeFormat) as e:
             error = e.get_HTTP()
             return error_response(error)
     else:
@@ -110,6 +114,7 @@ def items_post_check_support(id_, object_, class_path, path, is_collection):
 def items_put_check_support(id_, class_path, path, is_collection):
     """Check if class_type supports PUT operation"""
     object_ = json.loads(request.data.decode("utf-8"))
+    doc = get_doc()
     collections, parsed_classes = get_collections_and_parsed_classes()
     if path in parsed_classes:
         class_path = path
@@ -126,6 +131,7 @@ def items_put_check_support(id_, class_path, path, is_collection):
         try:
             # Add the object with given ID
             object_id = crud.insert(
+                doc,
                 object_=object_,
                 id_=id_,
                 session=get_session(),
@@ -144,7 +150,8 @@ def items_put_check_support(id_, class_path, path, is_collection):
             return set_response_headers(
                 jsonify(status_response), headers=headers_, status_code=status.code
             )
-        except (ClassNotFound, InstanceExists, PropertyNotFound) as e:
+        except (ClassNotFound, InstanceExists,
+                PropertyNotFound, InvalidDateTimeFormat) as e:
             error = e.get_HTTP()
             return error_response(error)
     else:
diff --git a/hydrus/data/helpers/items_helpers.py b/hydrus/data/helpers/items_helpers.py
index 898f7ff..d9a2ac7 100644
--- a/hydrus/data/helpers/items_helpers.py
+++ b/hydrus/data/helpers/items_helpers.py
@@ -8,6 +8,7 @@ from hydrus.data.exceptions import (
     InstanceExists,
     PropertyNotFound,
     InstanceNotFound,
+    InvalidDateTimeFormat
 )
 from hydrus.data.helpers import (
     checkClassOp,
@@ -26,6 +27,7 @@ from hydrus.utils import (
     get_api_name,
     get_hydrus_server_url,
     set_response_headers,
+    get_doc
 )
 from hydrus.extensions.socketio_factory import socketio
 
@@ -42,6 +44,7 @@ def items_put_response(path: str, int_list="") -> Response:
     :rtype: Response
     """
     object_ = json.loads(request.data.decode("utf-8"))
+    doc = get_doc()
     object_ = object_["data"]
     _, parsed_classes = get_collections_and_parsed_classes()
     if path in parsed_classes:
@@ -65,7 +68,7 @@ def items_put_response(path: str, int_list="") -> Response:
                 try:
                     # Insert object and return location in Header
                     object_id = crud.insert_multiple(
-                        objects_=object_, session=get_session(), id_=int_list
+                        doc, objects_=object_, session=get_session(), id_=int_list
                     )
                     headers_ = [
                         {
@@ -100,7 +103,8 @@ def items_put_response(path: str, int_list="") -> Response:
                             headers=headers_,
                             status_code=status.code,
                         )
-                except (ClassNotFound, InstanceExists, PropertyNotFound) as e:
+                except (ClassNotFound, InstanceExists,
+                        PropertyNotFound, InvalidDateTimeFormat) as e:
                     error = e.get_HTTP()
                     return error_response(error)
 
diff --git a/hydrus/data/resource_based_classes.py b/hydrus/data/resource_based_classes.py
index 124b24b..9cd271d 100644
--- a/hydrus/data/resource_based_classes.py
+++ b/hydrus/data/resource_based_classes.py
@@ -4,6 +4,7 @@ resources in the provided API Doc.
 """
 import copy
 import uuid
+from dateutil import parser
 from hydrus.data.db_models import Resource
 from hydrus.data.exceptions import (
     ClassNotFound,
@@ -13,11 +14,13 @@ from hydrus.data.exceptions import (
     PropertyNotFound,
     PropertyNotGiven,
     MemberInstanceNotFound,
+    InvalidDateTimeFormat
 )
 from sqlalchemy import exists
 from sqlalchemy.exc import InvalidRequestError, IntegrityError
 from sqlalchemy.orm.scoping import scoped_session
 from sqlalchemy.orm.exc import NoResultFound
+from hydra_python_core.doc_writer import HydraDoc
 from typing import Dict, Any
 
 
@@ -34,6 +37,39 @@ def get_type(object_: Dict[str, Any]) -> str:
     return type_
 
 
+def get_modified_object(object_: Dict[str, Any], doc: HydraDoc, path: str) -> Dict[str, Any]:
+    """
+    Return the object after modifying properties.
+    :param object_: Dict containing object properties
+    :param doc: HydraDoc object
+    :param path: Path of the Class or Collection
+    :return: object_ containing properties
+    """
+    class_ = doc.parsed_classes[path]
+    properties = class_["class"].supportedProperty
+    datetimefields = []
+    for prop in properties:
+        kwargs = getattr(prop, 'kwargs', None)
+        range = kwargs.get('range')
+        title = getattr(prop, 'title')
+        if range is not None:
+            if "dateTime" in range:
+                datetimefield = title
+                datetimefields.append(datetimefield)
+    if len(datetimefields) != 0:
+        for field in datetimefields:
+            try:
+                datetime_value = object_.get(field)
+                dt_object = parser.isoparse(datetime_value)
+                object_[field] = dt_object
+            except ValueError:
+                raise InvalidDateTimeFormat(field)
+            except TypeError:
+                datetime_value = object_.get(field)
+                object_[field] = datetime_value
+    return object_
+
+
 def get_database_class(type_: str):
     """
     Get the sqlalchemy class object from given classname
@@ -47,10 +83,11 @@ def get_database_class(type_: str):
 
 
 def insert_object(
-    object_: Dict[str, Any], session: scoped_session, collection: bool = False
+    doc_: HydraDoc, object_: Dict[str, Any], session: scoped_session, collection: bool = False
 ) -> str:
     """
     Insert the object in the database
+    :param doc_: HydraDoc object
     :param object_: Dict containing object properties
     :param session: sqlalchemy session
     :return: The ID of the inserted object
@@ -94,16 +131,20 @@ def insert_object(
             fk_column = fk.info["column_name"]
             try:
                 fk_object = object_[fk_column]
+                fk_object_path = fk_object.get('@type')
+                fk_object = get_modified_object(fk_object, doc_, fk_object_path)
             except KeyError as e:
                 wrong_property = e.args[0]
                 raise PropertyNotGiven(type_=wrong_property)
             # insert the foreign key object
-            fk_object_id = insert_object(fk_object, session)
+            fk_object_id = insert_object(doc_, fk_object, session)
             # put the id of the foreign instance in this table's column
             object_[fk_column] = fk_object_id
         try:
             # remove the @type from object before using the object to make a
             # instance of it using sqlalchemy class
+            object_path = object_.get('@type')
+            object_ = get_modified_object(object_, doc_, object_path)
             object_.pop("@type")
             inserted_object = database_class(**object_)
         except TypeError as e:
@@ -198,6 +239,7 @@ def delete_object(
 
 
 def update_object(
+    doc_: HydraDoc,
     object_: Dict[str, Any],
     query_info: Dict[str, str],
     session: scoped_session,
@@ -205,6 +247,7 @@ def update_object(
 ) -> str:
     """
     Update the object from the database
+    :param doc_: HydraDoc object
     :param object_: Dict containing updated object properties
     :param query_info: Dict containing the id and @type of object that has to retrieved
     :param session: sqlalchemy session
@@ -218,11 +261,11 @@ def update_object(
     # Try inserting new object
     try:
         object_["id"] = id_
-        d = insert_object(object_, session, collection)
+        d = insert_object(doc_, object_, session, collection)
     except Exception as e:
         # Put old object back
         old_object["id"] = id_
-        d = insert_object(old_object, session, collection)
+        d = insert_object(doc_, old_object, session, collection)
         raise e
     return id_
 
diff --git a/hydrus/samples/doc_writer_sample.py b/hydrus/samples/doc_writer_sample.py
index 113d8ff..7c9d163 100644
--- a/hydrus/samples/doc_writer_sample.py
+++ b/hydrus/samples/doc_writer_sample.py
@@ -81,14 +81,14 @@ prop1_uri = "http://props.hydrus.com/prop1"
 prop1_title = "Prop1"                   # Title of the property
 
 dummyProp1 = HydraClassProp(prop1_uri, prop1_title,
-                            required=False, read=False, write=True)
+                            required=False, read=False, write=True, range="xsd:dateTime")
 
 
 prop2_uri = "http://props.hydrus.com/prop2"
 prop2_title = "Prop2"
 
 dummyProp2 = HydraClassProp(prop2_uri, prop2_title,
-                            required=False, read=False, write=True)
+                            required=False, read=False, write=True, range="xsd:float")
 # NOTE: Properties that are required=True must be added during class object creation
 #       Properties that are read=True are read only
 #       Properties that are write=True are writable
diff --git a/hydrus/samples/doc_writer_sample_output.py b/hydrus/samples/doc_writer_sample_output.py
index d6b383c..7922a4a 100644
--- a/hydrus/samples/doc_writer_sample_output.py
+++ b/hydrus/samples/doc_writer_sample_output.py
@@ -151,6 +151,7 @@ doc = {
                 {
                     "@type": "SupportedProperty",
                     "property": "http://props.hydrus.com/prop1",
+                    "range": "xsd:dateTime",
                     "readable": "false",
                     "required": "false",
                     "title": "Prop1",
@@ -159,6 +160,7 @@ doc = {
                 {
                     "@type": "SupportedProperty",
                     "property": "http://props.hydrus.com/prop2",
+                    "range": "xsd:float",
                     "readable": "false",
                     "required": "false",
                     "title": "Prop2",
@@ -257,6 +259,7 @@ doc = {
                 {
                     "@type": "SupportedProperty",
                     "property": "http://props.hydrus.com/prop1",
+                    "range": "xsd:dateTime",
                     "readable": "false",
                     "required": "false",
                     "title": "Prop1",
@@ -265,6 +268,7 @@ doc = {
                 {
                     "@type": "SupportedProperty",
                     "property": "http://props.hydrus.com/prop2",
+                    "range": "xsd:float",
                     "readable": "false",
                     "required": "false",
                     "title": "Prop2",
@@ -325,6 +329,7 @@ doc = {
                 {
                     "@type": "SupportedProperty",
                     "property": "http://props.hydrus.com/prop1",
+                    "range": "xsd:dateTime",
                     "readable": "false",
                     "required": "false",
                     "title": "Prop1",


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
index b7ebe5d..5f419f3 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -2,6 +2,7 @@ import random
 import string
 import uuid
 from base64 import b64encode
+from datetime import datetime
 
 import pytest
 from hydra_python_core import doc_maker
@@ -64,6 +65,7 @@ def gen_dummy_object(class_title, doc):
                 member = gen_dummy_object(member_class, doc)
                 member_id = crud.insert(object_=member,
                                         session=get_session(),
+                                        doc_=doc,
                                         collection=False)
                 from hydrus.data.helpers import get_path_from_type
                 member_class_path = get_path_from_type(member_class)
@@ -89,7 +91,15 @@ def gen_dummy_object(class_title, doc):
                 else:
                     type_ = prop.kwargs.get('range')
                     if type_ is not None:
-                        object_[prop.title] = random.randint(50,100)
+                        if "dateTime" in type_:
+                            value = datetime.utcnow()
+                            obj = value.isoformat()
+                            object_[prop.title] = obj
+                        elif "string" in type_:
+                            object_[prop.title] = ''.join(random.choice(
+                            string.ascii_uppercase + string.digits) for _ in range(6))
+                        else:
+                            object_[prop.title] = random.randint(50,100)
                     else:
                         object_[prop.title] = ''.join(random.choice(
                             string.ascii_uppercase + string.digits) for _ in range(6))
@@ -300,11 +310,11 @@ def init_db_for_app_tests(doc, constants, session, add_doc_classes_and_propertie
     for class_ in doc.parsed_classes:
         class_title = doc.parsed_classes[class_]['class'].title
         dummy_obj = gen_dummy_object(class_title, doc)
-        crud.insert(dummy_obj, id_=str(uuid.uuid4()), session=session)
+        crud.insert(doc, dummy_obj, id_=str(uuid.uuid4()), session=session)
         # If it's a collection class then add an extra object so
         # we can test pagination thoroughly.
         if class_ in doc.collections:
-            crud.insert(dummy_obj, id_=str(uuid.uuid4()), session=session)
+            crud.insert(doc, dummy_obj, id_=str(uuid.uuid4()), session=session )
 
 
 @pytest.fixture()
@@ -316,11 +326,11 @@ def init_db_for_socket_tests(doc, add_doc_classes_and_properties_to_db, session)
     for class_ in doc.parsed_classes:
         class_title = doc.parsed_classes[class_]["class"].title
         dummy_obj = gen_dummy_object(class_title, doc)
-        crud.insert(dummy_obj, id_=str(uuid.uuid4()), session=session)
+        crud.insert(doc, dummy_obj, id_=str(uuid.uuid4()), session=session)
         # If it's a collection class then add an extra object so
         # we can test pagination thoroughly.
         if class_ in doc.collections:
-            crud.insert(dummy_obj, id_=str(uuid.uuid4()), session=session)
+            crud.insert(doc, dummy_obj, id_=str(uuid.uuid4()), session=session)
     # Add two dummy modification records
     crud.insert_modification_record(method="POST", resource_url="", session=session)
     crud.insert_modification_record(method="DELETE", resource_url="", session=session)
diff --git a/tests/unit/test_crud.py b/tests/unit/test_crud.py
index 6bc9c5d..1887016 100644
--- a/tests/unit/test_crud.py
+++ b/tests/unit/test_crud.py
@@ -17,7 +17,7 @@ def test_crud_insert_response_is_str(drone_doc_parsed_classes, drone_doc, sessio
     """Test CRUD insert response is string"""
     object_ = gen_dummy_object(random.choice(drone_doc_parsed_classes), drone_doc)
     id_ = str(uuid.uuid4())
-    response = crud.insert(object_=object_, id_=id_, session=session)
+    response = crud.insert(drone_doc, object_=object_, id_=id_, session=session)
 
     assert isinstance(response, str)
 
@@ -26,7 +26,7 @@ def test_crud_get_returns_correct_object(drone_doc_parsed_classes, drone_doc, se
     """Test CRUD get returns correct object"""
     object_ = gen_dummy_object(random.choice(drone_doc_parsed_classes), drone_doc)
     id_ = str(uuid.uuid4())
-    response = crud.insert(object_=object_, id_=id_, session=session)
+    response = crud.insert(drone_doc, object_=object_, id_=id_, session=session)
     object_ = crud.get(id_=id_, type_=object_['@type'], session=session, api_name='api')
     assert isinstance(response, str)
     assert object_['@id'].split('/')[-1] == id_
@@ -42,7 +42,7 @@ def test_get_for_nested_obj(drone_doc_parsed_classes, drone_doc, session, consta
                     dummy_obj = gen_dummy_object(class_, drone_doc)
                     nested_class = prop.prop.split(expanded_base_url)[1]
                     obj_id = str(uuid.uuid4())
-                    response = crud.insert(object_=dummy_obj, id_=obj_id, session=session)
+                    response = crud.insert(drone_doc, object_=dummy_obj, id_=obj_id, session=session)
                     object_ = crud.get(id_=obj_id, type_=class_, session=session,
                                        api_name='api')
                     assert prop.title in object_
@@ -91,7 +91,7 @@ def test_searching_over_collection_elements(drone_doc_parsed_classes, drone_doc,
 
                 obj_id = str(uuid.uuid4())
                 response = crud.insert(
-                    object_=object_, id_=obj_id, session=session)
+                    drone_doc, object_=object_, id_=obj_id, session=session)
                 search_result = crud.get_collection(API_NAME='api', type_=class_,
                                                     session=session, paginate=True,
                                                     page_size=5, search_params=search_params)
@@ -108,8 +108,9 @@ def test_update_on_object(drone_doc_parsed_classes, drone_doc, session):
     object_ = gen_dummy_object(random_class, drone_doc)
     new_object = gen_dummy_object(random_class, drone_doc)
     id_ = str(uuid.uuid4())
-    insert_response = crud.insert(object_=object_, id_=id_, session=session)
+    insert_response = crud.insert(drone_doc, object_=object_, id_=id_, session=session)
     update_response = crud.update(
+        drone_doc, 
         id_=id_,
         type_=object_['@type'],
         object_=new_object,
@@ -126,7 +127,7 @@ def test_delete_on_object(drone_doc_parsed_classes, drone_doc, session):
     """Test CRUD delete on object"""
     object_ = gen_dummy_object(random.choice(drone_doc_parsed_classes), drone_doc)
     id_ = str(uuid.uuid4())
-    insert_response = crud.insert(object_=object_, id_=id_, session=session)
+    insert_response = crud.insert(drone_doc, object_=object_, id_=id_, session=session)
     assert isinstance(insert_response, str)
     delete_response = crud.delete(id_=id_, type_=object_['@type'], session=session)
     response_code = None
@@ -160,7 +161,7 @@ def test_delete_on_wrong_id(drone_doc_parsed_classes, drone_doc, session):
     object_ = gen_dummy_object(random.choice(
         drone_doc_parsed_classes), drone_doc)
     id_ = str(uuid.uuid4())
-    insert_response = crud.insert(object_=object_, id_=id_, session=session)
+    insert_response = crud.insert(drone_doc, object_=object_, id_=id_, session=session)
     response_code = None
     try:
         delete_response = crud.delete(id_=999, type_=object_['@type'], session=session)
@@ -176,11 +177,11 @@ def test_insert_used_id(drone_doc_parsed_classes, drone_doc, session):
     """Test CRUD insert when used ID is given."""
     object_ = gen_dummy_object(random.choice(drone_doc_parsed_classes), drone_doc)
     id_ = str(uuid.uuid4())
-    insert_response = crud.insert(object_=object_, id_=id_, session=session)
+    insert_response = crud.insert(drone_doc, object_=object_, id_=id_, session=session)
     response_code = None
     try:
         insert_response = crud.insert(
-            object_=object_, id_=id_, session=session)
+            drone_doc, object_=object_, id_=id_, session=session)
     except Exception as e:
         error = e.get_HTTP()
         response_code = error.code
@@ -203,7 +204,7 @@ def test_delete_on_wrong_type(drone_doc_parsed_classes, drone_doc, session):
     """Test CRUD delete when wrong/undefined class is given."""
     object_ = gen_dummy_object(random.choice(drone_doc_parsed_classes), drone_doc)
     id_ = str(uuid.uuid4())
-    insert_response = crud.insert(object_=object_, id_=id_, session=session)
+    insert_response = crud.insert(drone_doc, object_=object_, id_=id_, session=session)
     assert isinstance(insert_response, str)
     assert insert_response == id_
     response_code = None
@@ -222,7 +223,7 @@ def test_insert_on_wrong_type(drone_doc_parsed_classes, drone_doc, session):
     object_['@type'] = 'otherClass'
     response_code = None
     try:
-        insert_response = crud.insert(object_=object_, id_=id_, session=session)
+        insert_response = crud.insert(drone_doc, object_=object_, id_=id_, session=session)
     except Exception as e:
         error = e.get_HTTP()
         response_code = error.code
@@ -237,7 +238,7 @@ def test_insert_multiple_id(drone_doc_parsed_classes, drone_doc, session):
     for index in range(len(ids_list)):
         object = gen_dummy_object(random.choice(drone_doc_parsed_classes), drone_doc)
         objects.append(object)
-    insert_response = crud.insert_multiple(objects_=objects, session=session, id_=ids)
+    insert_response = crud.insert_multiple(drone_doc, objects_=objects, session=session, id_=ids)
     for id_ in ids_list:
         assert id_ in insert_response
 
@@ -250,7 +251,7 @@ def test_delete_multiple_id(drone_doc_parsed_classes, drone_doc, session):
     for index in range(len(ids.split(','))):
         object = gen_dummy_object(random_class, drone_doc)
         objects.append(object)
-    insert_response = crud.insert_multiple(objects_=objects, session=session, id_=ids)
+    insert_response = crud.insert_multiple(drone_doc, objects_=objects, session=session, id_=ids)
     delete_response = crud.delete_multiple(id_=ids, type_=random_class, session=session)
 
     response_code = None
@@ -283,7 +284,7 @@ def test_insert_when_property_not_given(drone_doc_parsed_classes, drone_doc,
     dummy_obj.pop(nested_prop_title)
     id_ = str(uuid.uuid4())
     try:
-        insert_response = crud.insert(object_=dummy_obj, id_=id_, session=session)
+        insert_response = crud.insert(drone_doc, object_=dummy_obj, id_=id_, session=session)
     except PropertyNotGiven as e:
         error = e.get_HTTP()
         response_code = error.code