Repository: HTTP-APIs/hydrus (python)

Issue:
Add IRIs for created objects in response for POST and PUT request.
### I'm submitting a 
- [ ] bug report.
- [x] feature request.

### Current Behaviour:
<!-- Describe about the bug -->
After discussion on slack group we realised that some more details can be added in the response that is returned after a new object is created or updated using PUT/POST request. Currently, the response is in this format:
```
{
    "@context": "http://www.w3.org/ns/hydra/context.jsonld",
    "@type": "Status",
    "description": "Object with ID f3744cd9-1921-49f6-9caa-c6676678b029 successfully added",
    "statusCode": 201,
    "title": "Object successfully added."
}
```
We can add full IRIs for the created object as well, which will make it easier to reference the link or perform operations on the newly created/updated object.

### Expected Behaviour:
<!-- Describe what will happen if bug is removed -->
IRI for the object can be added in the response body.
```
{
    "@context": "http://www.w3.org/ns/hydra/context.jsonld",
    "@type": "Status",
    "description": "Object with ID f3744cd9-1921-49f6-9caa-c6676678b029 successfully added",
    "iri": "http://localhost:8080/serverapi/Movie/f3744cd9-1921-49f6-9caa-c6676678b029",
    "statusCode": 201,
    "title": "Object successfully added."
}
```

### Steps to reproduce:
<!-- If you can then please provide the steps to reproduce the bug -->
Make a PUT/POST request to any Class or Collection endpoint.

### 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:
Added full IRIs in response for POST and PUT requests.
<!-- 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 #573 

### 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 -->
Presently, we do not return any IRI or id for created/updated objects in response schema that is returned after a POST or PUT request.
```
{
    "@context": "http://www.w3.org/ns/hydra/context.jsonld",
    "@type": "Status",
    "description": "Object with ID f3744cd9-1921-49f6-9caa-c6676678b029 successfully added",
    "statusCode": 201,
    "title": "Object successfully added."
}
```

### New expected behaviour
<!-- Describe the new code and its expected behaviour -->
The format of response after the changes:
```
{
    "@context": "http://www.w3.org/ns/hydra/context.jsonld",
    "@type": "Status",
    "description": "Object with ID f3744cd9-1921-49f6-9caa-c6676678b029 successfully added",
    "iri": "http://localhost:8080/serverapi/Movie/f3744cd9-1921-49f6-9caa-c6676678b029",
    "statusCode": 201,
    "title": "Object successfully added."
}
```
Also a list of IRIs is returned, in case a PUT request is made to add multiple objects. 
Response schema in the case of multiple ids :
```
{
    "@context": "http://www.w3.org/ns/hydra/context.jsonld",
    "@type": "Status",
    "description": "Objects with ID ['d82b5942-f8fe-483f-9d5a-87ce89e80026', 'e82b5942-f8fe-483f-9d5a-87ce89e80026'] successfully added",
    "iri": [
        "http://localhost:8080/serverapi/Movie/d82b5942-f8fe-483f-9d5a-87ce89e80026",
        "http://localhost:8080/serverapi/Movie/e82b5942-f8fe-483f-9d5a-87ce89e80026"
    ],
    "statusCode": 201,
    "title": "Objects successfully added"
}
```
### Change logs

<!-- #### Added -->
<!-- Edit these points below to describe the new features added with this PR -->

- Added IRIs in the generated response for POST and PUT requests.
- Added IRIs in response for multiple objects.

<!-- - 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 -->


Patch:
diff --git a/hydrus/helpers.py b/hydrus/helpers.py
index 658a3eb..8c93d21 100644
--- a/hydrus/helpers.py
+++ b/hydrus/helpers.py
@@ -37,6 +37,18 @@ def validObjectList(objects_: List[Dict[str, Any]]) -> bool:
     return True
 
 
+def get_iri_from_int_list(path_url: str, int_list: List[str]) -> List[str]:
+    """Returns a list of full IRIs of multiple objects from int_list
+    :param path_url: The path of the object
+    :param int_list: Optional String containing ',' separated ID's
+    :return: IRIs of multiple objects
+    """
+    iri_list = []
+    for id_ in int_list:
+        iri_list.append(f"{path_url}/{id_}")
+    return iri_list
+
+
 def type_match(object_: List[Dict[str, Any]], obj_type: str) -> bool:
     """
     Checks if the object type matches for every object in list.
diff --git a/hydrus/item_collection_helpers.py b/hydrus/item_collection_helpers.py
index 6bdde64..6afb8c5 100644
--- a/hydrus/item_collection_helpers.py
+++ b/hydrus/item_collection_helpers.py
@@ -116,14 +116,16 @@ def item_collection_put_response(path: str) -> Response:
             # Insert object and return location in Header
             object_id = crud.insert(object_=object_, session=get_session(),
                                     collection=is_collection)
-            headers_ = [
-                {"Location": f"{get_hydrus_server_url()}{get_api_name()}/{path}/{object_id}"}]
+            resource_url = f"{get_hydrus_server_url()}{get_api_name()}/{path}/{object_id}"
+            headers_ = [{"Location": resource_url}]
             status_description = f"Object with ID {object_id} successfully added"
             status = HydraStatus(code=201,
                                  title="Object successfully added",
                                  desc=status_description)
+            status_response = status.generate()
+            status_response["iri"] = resource_url
             return set_response_headers(
-                jsonify(status.generate()), headers=headers_,
+                jsonify(status_response), headers=headers_,
                 status_code=status.code)
         except (ClassNotFound, InstanceExists, PropertyNotFound,
                 PropertyNotGiven) as e:
diff --git a/hydrus/itemhelpers.py b/hydrus/itemhelpers.py
index c7faa8f..64e26f7 100644
--- a/hydrus/itemhelpers.py
+++ b/hydrus/itemhelpers.py
@@ -90,7 +90,9 @@ def items_post_check_support(id_, object_, class_path, path, is_collection):
                                   "updated")
             status = HydraStatus(
                 code=200, title="Object updated", desc=status_description)
-            return set_response_headers(jsonify(status.generate()),
+            status_response = status.generate()
+            status_response["iri"] = resource_url
+            return set_response_headers(jsonify(status_response),
                                         headers=headers_)
 
         except (ClassNotFound,
@@ -125,13 +127,15 @@ def items_put_check_support(id_, class_path, path, is_collection):
             # Add the object with given ID
             object_id = crud.insert(object_=object_, id_=id_,
                                     session=get_session(), collection=is_collection)
-            headers_ = [{"Location": f"{get_hydrus_server_url()}"
-                                     f"{get_api_name()}/{path}/{object_id}"}]
+            resource_url = f"{get_hydrus_server_url()}{get_api_name()}/{path}/{object_id}"
+            headers_ = [{"Location": resource_url}]
             status_description = f"Object with ID {object_id} successfully added"
             status = HydraStatus(code=201, title="Object successfully added.",
                                  desc=status_description)
+            status_response = status.generate()
+            status_response["iri"] = resource_url
             return set_response_headers(
-                jsonify(status.generate()), headers=headers_,
+                jsonify(status_response), headers=headers_,
                 status_code=status.code)
         except (ClassNotFound, InstanceExists, PropertyNotFound) as e:
             error = e.get_HTTP()
diff --git a/hydrus/items_helpers.py b/hydrus/items_helpers.py
index e334270..b278a95 100644
--- a/hydrus/items_helpers.py
+++ b/hydrus/items_helpers.py
@@ -17,6 +17,7 @@ from hydrus.helpers import (
     check_required_props,
     send_sync_update,
     get_link_props_for_multiple_objects,
+    get_iri_from_int_list,
     error_response,
     getType,
 )
@@ -75,11 +76,15 @@ def items_put_response(path: str, int_list="") -> Response:
                             jsonify(response), headers=headers_,
                             status_code=status.code)
                     else:
+                        path_url = f"{get_hydrus_server_url()}{get_api_name()}/{path}"
+                        response_url_list = get_iri_from_int_list(path_url, object_id)
                         status_description = f"Objects with ID {object_id} successfully added"
                         status = HydraStatus(code=201, title="Objects successfully added",
                                              desc=status_description)
+                        status_response = status.generate()
+                        status_response["iri"] = response_url_list
                         return set_response_headers(
-                            jsonify(status.generate()), headers=headers_,
+                            jsonify(status_response), headers=headers_,
                             status_code=status.code)
                 except (ClassNotFound, InstanceExists, PropertyNotFound) as e:
                     error = e.get_HTTP()
diff --git a/hydrus/resources.py b/hydrus/resources.py
index e5ec90b..dbc36de 100644
--- a/hydrus/resources.py
+++ b/hydrus/resources.py
@@ -248,6 +248,8 @@ class ItemMember(Resource):
 
 
 class Items(Resource):
+    """Handles operations(PUT,DELETE) related to multiple objects.
+    (Item should be hydra:Class)"""
     @authenticate
     def put(self, path, int_list="") -> Response:
         """


Test patch:
diff --git a/hydrus/tests/functional/test_app.py b/hydrus/tests/functional/test_app.py
index 60d46c5..119c2b7 100644
--- a/hydrus/tests/functional/test_app.py
+++ b/hydrus/tests/functional/test_app.py
@@ -134,6 +134,7 @@ class TestApp():
                 good_response_put = test_app_client.put(endpoint['@id'],
                                                         data=json.dumps(dummy_object))
                 assert good_response_put.status_code == 201
+                assert good_response_put.json["iri"] == good_response_put.location
 
     def test_Collections_constraint_PUT(self, test_app_client, constants, doc):
         """Test collection constraints by inserting same object twice."""
@@ -150,6 +151,7 @@ class TestApp():
                 good_response_put = test_app_client.put(endpoint['@id'],
                                                         data=json.dumps(dummy_object))
                 assert good_response_put.status_code == 201
+                assert good_response_put.json["iri"] == good_response_put.location
                 collection_id = good_response_put.location
                 bad_response_put = test_app_client.put(collection_id,
                                                        data=json.dumps(dummy_object))
@@ -200,6 +202,7 @@ class TestApp():
                     collection_id, data=json.dumps(dummy_object_2))
                 assert second_put_response.status_code == 201
                 assert second_put_response.location == collection_id
+                assert second_put_response.json["iri"] == collection_id
 
     def test_collection_object_POST(self, test_app_client, constants, doc, socketio):
         """Test POST of a given collection object using ID."""
@@ -215,6 +218,7 @@ class TestApp():
             initial_put_response = test_app_client.put(
                 endpoint['@id'], data=json.dumps(dummy_object))
             assert initial_put_response.status_code == 201
+            assert initial_put_response.json["iri"] == initial_put_response.location
             response = json.loads(initial_put_response.data.decode('utf-8'))
             regex = r'(.*)ID (.{36})* (.*)'
             matchObj = re.match(regex, response['description'])
@@ -272,6 +276,7 @@ class TestApp():
                 put_response = test_app_client.put(f'{endpoint["@id"]}/{uuid.uuid4()}',
                                                    data=json.dumps(dummy_object))
                 assert put_response.status_code == 201
+                assert put_response.json["iri"] == put_response.location
 
     def test_object_PUT_at_ids(self, test_app_client, constants, doc):
         API_NAME = constants['API_NAME']
@@ -295,6 +300,7 @@ class TestApp():
                         put_response = test_app_client.put(f'{endpoints[endpoint]}/add/{ids}',
                                                            data=json.dumps(data_))
                         assert put_response.status_code == 201
+                        assert isinstance(put_response.json['iri'], list)
 
     def test_endpointClass_PUT(self, test_app_client, constants, doc):
         """Check non collection Class PUT."""
@@ -315,6 +321,7 @@ class TestApp():
                         put_response = test_app_client.put(endpoints[endpoint],
                                                            data=json.dumps(dummy_object))
                         assert put_response.status_code == 201
+                        assert put_response.json["iri"] == put_response.location
 
     def test_endpointClass_POST(self, test_app_client, constants, doc):
         """Check non collection Class POST."""