contentgrid-application-client 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. contentgrid_application_client-0.0.1/LICENSE +13 -0
  2. contentgrid_application_client-0.0.1/PKG-INFO +95 -0
  3. contentgrid_application_client-0.0.1/README.md +69 -0
  4. contentgrid_application_client-0.0.1/pyproject.toml +23 -0
  5. contentgrid_application_client-0.0.1/pytest.ini +5 -0
  6. contentgrid_application_client-0.0.1/requirements.txt +1 -0
  7. contentgrid_application_client-0.0.1/setup.cfg +4 -0
  8. contentgrid_application_client-0.0.1/setup.py +11 -0
  9. contentgrid_application_client-0.0.1/src/contentgrid_application_client/__init__.py +1 -0
  10. contentgrid_application_client-0.0.1/src/contentgrid_application_client/application.py +328 -0
  11. contentgrid_application_client-0.0.1/src/contentgrid_application_client.egg-info/PKG-INFO +95 -0
  12. contentgrid_application_client-0.0.1/src/contentgrid_application_client.egg-info/SOURCES.txt +24 -0
  13. contentgrid_application_client-0.0.1/src/contentgrid_application_client.egg-info/dependency_links.txt +1 -0
  14. contentgrid_application_client-0.0.1/src/contentgrid_application_client.egg-info/requires.txt +1 -0
  15. contentgrid_application_client-0.0.1/src/contentgrid_application_client.egg-info/top_level.txt +1 -0
  16. contentgrid_application_client-0.0.1/tests/example-docs/document.jpg +0 -0
  17. contentgrid_application_client-0.0.1/tests/example-docs/resume.pdf +0 -0
  18. contentgrid_application_client-0.0.1/tests/fixtures.py +34 -0
  19. contentgrid_application_client-0.0.1/tests/test_collection.py +36 -0
  20. contentgrid_application_client-0.0.1/tests/test_content.py +94 -0
  21. contentgrid_application_client-0.0.1/tests/test_curies.py +18 -0
  22. contentgrid_application_client-0.0.1/tests/test_entity_creation.py +129 -0
  23. contentgrid_application_client-0.0.1/tests/test_hal_form_type_check.py +20 -0
  24. contentgrid_application_client-0.0.1/tests/test_openapi.py +12 -0
  25. contentgrid_application_client-0.0.1/tests/test_profile.py +24 -0
  26. contentgrid_application_client-0.0.1/tests/test_relations.py +54 -0
@@ -0,0 +1,13 @@
1
+ Copyright 2024 Xenit Solutions
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
@@ -0,0 +1,95 @@
1
+ Metadata-Version: 2.1
2
+ Name: contentgrid-application-client
3
+ Version: 0.0.1
4
+ Summary: Python Client for interacting with ContentGrid Applications
5
+ Author-email: Ranec Belpaire <ranec.belpaire@xenit.eu>
6
+ License: Copyright 2024 Xenit Solutions
7
+
8
+ Licensed under the Apache License, Version 2.0 (the "License");
9
+ you may not use this file except in compliance with the License.
10
+ You may obtain a copy of the License at
11
+
12
+ http://www.apache.org/licenses/LICENSE-2.0
13
+
14
+ Unless required by applicable law or agreed to in writing, software
15
+ distributed under the License is distributed on an "AS IS" BASIS,
16
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ See the License for the specific language governing permissions and
18
+ limitations under the License.
19
+ Classifier: Development Status :: 3 - Alpha
20
+ Classifier: Operating System :: OS Independent
21
+ Classifier: Programming Language :: Python :: 3
22
+ Requires-Python: >=3.5
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: contentgrid-hal-client==0.0.1
26
+
27
+ # ContentGrid Python Client
28
+
29
+ This ContentGrid Client is a Python library designed to interact with ContentGrid API endpoints, specifically in the HAL response type. For ContentGrid Applications, it provides a convenient interface for performing various operations such as fetching profiles, creating entities, fetching related entities, handling content attributes, and more.
30
+
31
+ ## Features
32
+
33
+ - **Profile Handling**: Fetch profiles to retrieve HAL-forms specification about available entities and their attributes.
34
+ - **Entity Operations**: Create, fetch, update, and delete entities using a simple functions without needing to worry about headers and authentication.
35
+ - **Content Handling**: Upload and download content on content-attributes of entities.
36
+ - **Error Handling**: Provides basic error handling for network requests.
37
+ - **Attribute validation**: Provides attribute type checks for creating and updating entities. Checks wether attributes have the correct type and if all required attributes are present.
38
+
39
+ ## Installation
40
+
41
+ To install the ContentGrid API Client, you can use pip:
42
+
43
+ ```bash
44
+ pip install contentgrid-application-client
45
+ ```
46
+
47
+ ## Usage
48
+
49
+ ### ContentGridApplicationClient
50
+ ```python
51
+ from contentgrid-application-client import ContentGridApplicationClient
52
+
53
+ # Initialize the client with service account
54
+ client = ContentGridApplicationClient(
55
+ client_endpoint="https://b93ccecf-3466-44c0-995e-2620a8c66ac3.eu-west-1.contentgrid.cloud",
56
+ auth_uri="https://auth.eu-west-1.contentgrid.cloud/realms/cg-eade54da-3903-4554-aa5e-2982cd4126f1/protocol/openid-connect/token",
57
+ client_id="your_client_id",
58
+ client_secret="your_client_secret"
59
+ )
60
+
61
+ # Initialize the client with token
62
+ client = ContentGridApplicationClient(
63
+ client_endpoint="https://b93ccecf-3466-44c0-995e-2620a8c66ac3.eu-west-1.contentgrid.cloud",
64
+ token="your_token"
65
+ )
66
+
67
+ # Fetch profile
68
+ profile = client.get_profile()
69
+
70
+ # Create entity
71
+ attributes = {
72
+ "name": "Example Entity",
73
+ "description": "This is an example entity"
74
+ }
75
+ entity = client.create_entity("entity-name", attributes)
76
+ ```
77
+
78
+ ## Testing
79
+ Installing requirements:
80
+ ```bash
81
+ pip install -r requirements.txt
82
+ ```
83
+
84
+ Running tests:
85
+
86
+ ```bash
87
+ python -m pytest
88
+ ```
89
+
90
+ Running tests with coverage:
91
+
92
+ ```bash
93
+ coverage run -m pytest && coverage report -m
94
+ ```
95
+
@@ -0,0 +1,69 @@
1
+ # ContentGrid Python Client
2
+
3
+ This ContentGrid Client is a Python library designed to interact with ContentGrid API endpoints, specifically in the HAL response type. For ContentGrid Applications, it provides a convenient interface for performing various operations such as fetching profiles, creating entities, fetching related entities, handling content attributes, and more.
4
+
5
+ ## Features
6
+
7
+ - **Profile Handling**: Fetch profiles to retrieve HAL-forms specification about available entities and their attributes.
8
+ - **Entity Operations**: Create, fetch, update, and delete entities using a simple functions without needing to worry about headers and authentication.
9
+ - **Content Handling**: Upload and download content on content-attributes of entities.
10
+ - **Error Handling**: Provides basic error handling for network requests.
11
+ - **Attribute validation**: Provides attribute type checks for creating and updating entities. Checks wether attributes have the correct type and if all required attributes are present.
12
+
13
+ ## Installation
14
+
15
+ To install the ContentGrid API Client, you can use pip:
16
+
17
+ ```bash
18
+ pip install contentgrid-application-client
19
+ ```
20
+
21
+ ## Usage
22
+
23
+ ### ContentGridApplicationClient
24
+ ```python
25
+ from contentgrid-application-client import ContentGridApplicationClient
26
+
27
+ # Initialize the client with service account
28
+ client = ContentGridApplicationClient(
29
+ client_endpoint="https://b93ccecf-3466-44c0-995e-2620a8c66ac3.eu-west-1.contentgrid.cloud",
30
+ auth_uri="https://auth.eu-west-1.contentgrid.cloud/realms/cg-eade54da-3903-4554-aa5e-2982cd4126f1/protocol/openid-connect/token",
31
+ client_id="your_client_id",
32
+ client_secret="your_client_secret"
33
+ )
34
+
35
+ # Initialize the client with token
36
+ client = ContentGridApplicationClient(
37
+ client_endpoint="https://b93ccecf-3466-44c0-995e-2620a8c66ac3.eu-west-1.contentgrid.cloud",
38
+ token="your_token"
39
+ )
40
+
41
+ # Fetch profile
42
+ profile = client.get_profile()
43
+
44
+ # Create entity
45
+ attributes = {
46
+ "name": "Example Entity",
47
+ "description": "This is an example entity"
48
+ }
49
+ entity = client.create_entity("entity-name", attributes)
50
+ ```
51
+
52
+ ## Testing
53
+ Installing requirements:
54
+ ```bash
55
+ pip install -r requirements.txt
56
+ ```
57
+
58
+ Running tests:
59
+
60
+ ```bash
61
+ python -m pytest
62
+ ```
63
+
64
+ Running tests with coverage:
65
+
66
+ ```bash
67
+ coverage run -m pytest && coverage report -m
68
+ ```
69
+
@@ -0,0 +1,23 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0.0", "wheel", "setuptools_scm>=8"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "contentgrid-application-client"
7
+ dynamic = ["version", "dependencies"]
8
+ description = "Python Client for interacting with ContentGrid Applications"
9
+ readme = "README.md"
10
+ authors = [{ name = "Ranec Belpaire", email = "ranec.belpaire@xenit.eu" }]
11
+ license = { file = "LICENSE" }
12
+
13
+ classifiers = [
14
+ "Development Status :: 3 - Alpha",
15
+ "Operating System :: OS Independent",
16
+ "Programming Language :: Python :: 3",
17
+ ]
18
+
19
+ requires-python = ">=3.5"
20
+
21
+ [tool.setuptools_scm]
22
+ write_to = "_version.txt"
23
+ root = "../"
@@ -0,0 +1,5 @@
1
+ [pytest]
2
+ log_cli = 1
3
+ log_cli_level = INFO
4
+ log_cli_format = %(asctime)s [%(levelname)s] %(message)s (%(filename)s:%(lineno)s)
5
+ log_cli_date_format=%Y-%m-%d %H:%M:%S
@@ -0,0 +1 @@
1
+ -e ../contentgrid_hal_client
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,11 @@
1
+ from setuptools import setup
2
+ import setuptools_scm
3
+
4
+ # Retrieve the version using setuptools_scm
5
+ version = setuptools_scm.get_version(root="../")
6
+
7
+ setup(
8
+ install_requires=[
9
+ f"contentgrid-hal-client=={version}",
10
+ ],
11
+ )
@@ -0,0 +1 @@
1
+ from .application import InteractiveApplicationResponse, EntityCollection, EntityObject, Profile, PageInfo, ContentGridApplicationClient, hal_form_type_check
@@ -0,0 +1,328 @@
1
+ from typing import List, Optional, Self
2
+ import requests
3
+ import os
4
+ import logging
5
+ import mimetypes
6
+
7
+ from contentgrid_hal_client.exceptions import BadRequest, IncorrectAttributeType, MissingRequiredAttribute, NotFound
8
+ from contentgrid_hal_client.hal import CurieRegistry, HALFormsClient, HALLink, HALResponse, InteractiveHALResponse
9
+ from datetime import datetime
10
+ from email.header import decode_header
11
+
12
+ logging.basicConfig(level=logging.DEBUG)
13
+
14
+ hal_form_type_check = {
15
+ "text" : (lambda value : type(value) == str),
16
+ "datetime" : (lambda value : is_valid_date_format(value)),
17
+ "checkbox" : (lambda value : type(value) == bool),
18
+ "number" : (lambda value : type(value) == int or type(value) == float),
19
+ "url" : (lambda value : value.startswith("http://") or value.startswith("https://"))
20
+ }
21
+
22
+ def is_valid_date_format(date_string):
23
+ try:
24
+ datetime.strptime(date_string, "%Y-%m-%dT%H:%M:%S.%fZ")
25
+ return True
26
+ except ValueError:
27
+ return False
28
+
29
+ hal_form_types = {
30
+ "text" : "string",
31
+ "datetime" : "date (e.g.:2024-03-20T16:48:59.904Z)",
32
+ "checkbox" : "boolean",
33
+ "number" : "int/float",
34
+ "url" : "url"
35
+ }
36
+
37
+ class InteractiveApplicationResponse(InteractiveHALResponse):
38
+ def __init__(self, data: dict, client: "ContentGridApplicationClient", curie_registry: CurieRegistry = None) -> None:
39
+ assert isinstance(client, ContentGridApplicationClient)
40
+ super().__init__(data, client, curie_registry)
41
+ self.client : ContentGridApplicationClient = client
42
+
43
+ class EntityCollection(InteractiveApplicationResponse):
44
+ def __init__(self, data: dict, client: HALFormsClient, curie_registry: CurieRegistry = None) -> None:
45
+ super().__init__(data, client, curie_registry)
46
+ self.page_info: PageInfo = None
47
+ if "page" in data.keys():
48
+ self.page_info = PageInfo(
49
+ size=data["page"]["size"],
50
+ total_elements=data["page"]["totalElements"],
51
+ total_pages=data["page"]["totalPages"],
52
+ number=data["page"]["number"],
53
+ )
54
+
55
+ def get_entities(self) -> Optional[List["EntityObject"]]:
56
+ return self.get_embedded_objects_by_key("item", infer_type=EntityObject)
57
+
58
+ def get_entity_profile_link(self) -> HALLink:
59
+ return self.get_link("profile")
60
+
61
+ def create_entity(self, attributes : dict, attribute_validation=True) -> "EntityObject":
62
+ self.client._transform_hal_links_to_uris(attributes=attributes)
63
+ if attribute_validation:
64
+ entity_profile = self.client.follow_link(self.get_entity_profile_link())
65
+ self.client._validate_params(entity_profile.templates["create-form"], attributes=attributes)
66
+ response = self.client.post(self.get_self_link().uri, json=attributes, headers={"Content-Type" : "application/json"})
67
+ data = self.client._validate_json_response(response)
68
+ return EntityObject(data=data, client=self.client)
69
+
70
+ def first(self):
71
+ if not self.has_link("first"):
72
+ raise Exception(f"Collection {self.get_self_link()} has no first page")
73
+ self.__init__(data=self.client.follow_link(self.get_link("first"), infer_type=EntityCollection).data, client=self.client, curie_registry=self.curie_registry)
74
+
75
+ def last(self):
76
+ if not self.has_link("last"):
77
+ raise Exception(f"Collection {self.get_self_link()} has no last page")
78
+ self.__init__(data=self.client.follow_link(self.get_link("last"), infer_type=EntityCollection).data, client=self.client, curie_registry=self.curie_registry)
79
+
80
+ def next(self):
81
+ if not self.has_link("next"):
82
+ raise Exception(f"Collection {self.get_self_link()} has no next page")
83
+ self.__init__(data=self.client.follow_link(self.get_link("next"), infer_type=EntityCollection).data, client=self.client, curie_registry=self.curie_registry)
84
+
85
+ def prev(self):
86
+ if not self.has_link("prev"):
87
+ raise Exception(f"Collection {self.get_self_link()} has no prev page")
88
+ self.__init__(data=self.client.follow_link(self.get_link("last"), infer_type=EntityCollection).data, client=self.client, curie_registry=self.curie_registry)
89
+
90
+
91
+ class EntityObject(InteractiveApplicationResponse):
92
+ def __init__(self, data: dict, client: "ContentGridApplicationClient", curie_registry: CurieRegistry = None) -> None:
93
+ super().__init__(data, client, curie_registry)
94
+ self.id = data["id"]
95
+
96
+ def get_content_links(self) -> List[HALLink]:
97
+ return self.get_links("https://contentgrid.cloud/rels/contentgrid/content")
98
+
99
+ def get_relation_links(self) -> List[HALLink]:
100
+ return self.get_links("https://contentgrid.cloud/rels/contentgrid/relation")
101
+
102
+ def get_relation_link(self, relation_name: str) -> HALLink:
103
+ relation_links = self.get_relation_links()
104
+ for relation_link in relation_links:
105
+ if relation_link.name == relation_name:
106
+ return relation_link
107
+ # If relation not found, raise exception.
108
+ raise NotFound(f"Relation {relation_name} not found on entity {self.get_self_link().uri}")
109
+
110
+ def get_relation_collection(self, relation_name : str, page: int = 0, size: int = 20, params: dict = {}):
111
+ params = self.client._add_page_and_size_to_params(page=page, size=size, params=params)
112
+ relation_link = self.get_relation_link(relation_name=relation_name)
113
+ return self.client.follow_link(relation_link, infer_type=EntityCollection, params=params)
114
+
115
+ def put_relation(self, relation_name: str, related_entity_links : List[str | HALLink | Self]) -> None:
116
+ relation_link = self.get_relation_link(relation_name=relation_name)
117
+ relation_payload = self._create_text_uri_list_payload(links=related_entity_links)
118
+ response = self.client.put(relation_link.uri, headers={"Accept" : "*/*", "Content-Type" : "text/uri-list"}, data=relation_payload)
119
+ self.client._validate_non_json_response(response)
120
+
121
+ def post_relation(self, relation_name: str, related_entity_links : List[str | HALLink | Self]) -> None:
122
+ relation_link = self.get_relation_link(relation_name=relation_name)
123
+ relation_payload = self._create_text_uri_list_payload(links=related_entity_links)
124
+ response = self.client.post(relation_link.uri, headers={"Accept" : "*/*", "Content-Type" : "text/uri-list"}, data=relation_payload)
125
+ self.client._validate_non_json_response(response)
126
+
127
+ def put_data(self, data: dict, attribute_validation=True) -> None:
128
+ self.client._transform_hal_links_to_uris(attributes=data)
129
+ if attribute_validation:
130
+ self.client._validate_params(self.templates["default"], attributes=data)
131
+ return super().put_data(data)
132
+
133
+ def patch_data(self, data : dict, attribute_validation=True ) -> None:
134
+ self.client._transform_hal_links_to_uris(attributes=data)
135
+ if attribute_validation:
136
+ self.client._validate_params(self.templates["default"], attributes=data)
137
+ return super().patch_data(data)
138
+
139
+ def put_content_attribute(self, content_attribute_name : str, filepath : str) -> HALLink:
140
+ content_links = self.get_content_links()
141
+ if len(content_links) > 0:
142
+ for content_link in content_links:
143
+ if content_link.name == content_attribute_name:
144
+ return self.client.put_on_content_link(content_link=content_link, filepath=filepath)
145
+ raise NotFound(f"Content Attribute {content_attribute_name} not found on entity {self.get_self_link().uri}")
146
+
147
+ def fetch_content_attribute_by_name(self, content_attribute_name : str) -> tuple[str, bytes]:
148
+ content_links = self.get_content_links()
149
+ if len(content_links) > 0:
150
+ for content_link in content_links:
151
+ if content_link.name == content_attribute_name:
152
+ return self.client.fetch_content_attribute(content_link=content_link)
153
+ raise NotFound(f"Content Attribute {content_attribute_name} not found on entity {self.get_self_link().uri}")
154
+
155
+ def fetch_all_content_attributes(self) -> List[tuple[str, bytes]]:
156
+ files = []
157
+ for hal_content_link in self.get_content_links():
158
+ if self.metadata[hal_content_link.name] != None:
159
+ files.append(self.client.fetch_content_attribute(hal_content_link))
160
+ return files
161
+
162
+ def _create_text_uri_list_payload(self, links : List[str | HALLink | HALResponse]) -> str:
163
+ uri_list = []
164
+ for link in links:
165
+ if isinstance(link, HALLink):
166
+ uri_list.append(link.uri)
167
+ elif isinstance(link, HALResponse):
168
+ uri_list.append(link.get_self_link().uri)
169
+ elif isinstance(link, str):
170
+ uri_list.append(link)
171
+ else:
172
+ raise BadRequest(f"Incorrect Link type {type(link)} in uri list payload, allowed types: HALLink, HALResponse or str")
173
+ return "\n".join(uri_list)
174
+
175
+ class Profile(InteractiveHALResponse):
176
+ def __init__(self, data: dict, client: HALFormsClient, curie_registry: CurieRegistry = None) -> None:
177
+ super().__init__(data, client, curie_registry)
178
+
179
+ def get_entity_links(self) -> List[HALLink]:
180
+ return self.get_links("https://contentgrid.cloud/rels/contentgrid/entity")
181
+
182
+ def get_entity_profile(self, pluralized_entity_name: str):
183
+ pluralized_entity_names = [entityprofile.name for entityprofile in self.get_entity_links()]
184
+ if pluralized_entity_name in pluralized_entity_names:
185
+ for entity_profile_link in self.get_entity_links():
186
+ if entity_profile_link.name == pluralized_entity_name:
187
+ return self.client.follow_link(entity_profile_link, expect_json=True)
188
+ else:
189
+ raise NotFound(f"Entity {pluralized_entity_name} does not exist.")
190
+
191
+ class PageInfo:
192
+ def __init__(self, size : int, total_elements : int, total_pages : int, number : int) -> None:
193
+ self.size : int = size
194
+ self.total_elements : int = total_elements
195
+ self.total_pages : int = total_pages
196
+ self.number : int = number
197
+
198
+ class ContentGridApplicationClient(HALFormsClient):
199
+ def __init__(self,
200
+ client_endpoint: str,
201
+ auth_uri: str = None,
202
+ client_id: str = None,
203
+ client_secret: str = None,
204
+ token: str = None,
205
+ attribute_validation : bool = True,
206
+ session_cookie: str = None,
207
+ pool_maxsize : int = 10,
208
+ ) -> None:
209
+ logging.info("Initializing ContentGridApplicationClient...")
210
+ super().__init__(client_endpoint=client_endpoint, auth_uri=auth_uri, client_id=client_id, client_secret=client_secret, token=token, session_cookie=session_cookie, pool_maxsize=pool_maxsize)
211
+ self.attribute_validation = attribute_validation
212
+
213
+ def get_profile(self) -> Profile:
214
+ response = self.get("/profile", headers={"Accept": "application/json"})
215
+ data = self._validate_json_response(response)
216
+ return Profile(data, client=self)
217
+
218
+ def get_entity_profile(self, pluralized_entity_name : str) -> HALResponse:
219
+ return self.get_profile().get_entity_profile(pluralized_entity_name=pluralized_entity_name)
220
+
221
+ def fetch_openapi_yaml(self) -> tuple[str, bytes]:
222
+ res = self.get("/openapi.yml")
223
+ self._validate_non_json_response(res)
224
+ return ("openapi.yml", res.content)
225
+
226
+ def get_entity_collection(self, plural_entity_name: str, page: int = 0, size: int = 20, params: dict = {}) -> EntityCollection:
227
+ params = self._add_page_and_size_to_params(page=page, size=size, params=params)
228
+ response = self.get(plural_entity_name, params=params)
229
+ data = self._validate_json_response(response)
230
+ return EntityCollection(data, client=self)
231
+
232
+ def create_entity(self, pluralized_entity_name: str, attributes:dict) -> EntityObject:
233
+ return self.get_entity_collection(plural_entity_name=pluralized_entity_name).create_entity(attributes=attributes, attribute_validation=self.attribute_validation)
234
+
235
+ def get_entity_instance(self, entity_link: HALLink | EntityObject) -> EntityObject:
236
+ if isinstance(entity_link, EntityObject):
237
+ return self.follow_link(entity_link.get_self_link(), infer_type=EntityObject)
238
+ elif isinstance(entity_link, HALLink):
239
+ return self.follow_link(entity_link, infer_type=EntityObject)
240
+ else:
241
+ raise BadRequest(f"entity_link should be of type EntityObject or HALLink. was type {type(entity_link)}")
242
+
243
+ def get_entity_relation_collection(self, entity_link: HALLink, relation_name : str, page: int = 0, size: int = 20, params: dict = {}) -> EntityCollection:
244
+ return self.get_entity_instance(entity_link=entity_link).get_relation_collection(relation_name=relation_name, page=page, size=size, params=params)
245
+
246
+ def put_entity_relation(self, entity_link: HALLink, relation_name: str, related_entity_links : List[str | HALLink | EntityObject]) -> None:
247
+ return self.get_entity_instance(entity_link=entity_link).put_relation(relation_name=relation_name, related_entity_links=related_entity_links)
248
+
249
+ def post_entity_relation(self, entity_link: HALLink, relation_name: str, related_entity_links : List[str | HALLink | EntityObject]) -> None:
250
+ return self.get_entity_instance(entity_link=entity_link).post_relation(relation_name=relation_name, related_entity_links=related_entity_links)
251
+
252
+ def put_entity_attributes(self, entity_link: HALLink, attributes: dict) -> EntityObject:
253
+ entity = self.get_entity_instance(entity_link=entity_link)
254
+ entity.put_data(data=attributes, attribute_validation=self.attribute_validation)
255
+ return entity
256
+
257
+ def patch_entity_attributes(self, entity_link: HALLink, attributes: dict) -> EntityObject:
258
+ entity = self.get_entity_instance(entity_link=entity_link)
259
+ entity.patch_data(data=attributes, attribute_validation=self.attribute_validation)
260
+ return entity
261
+
262
+ def put_content_attribute(self, entity_link: HALLink, content_attribute_name : str, filepath : str) -> HALLink:
263
+ return self.get_entity_instance(entity_link=entity_link).put_content_attribute(content_attribute_name=content_attribute_name, filepath=filepath)
264
+
265
+ def put_on_content_link(self, content_link:HALLink, filepath: str) -> HALLink:
266
+ if os.path.exists(filepath):
267
+ filename = filepath.split('/')[-1]
268
+ files = {'file': (filename, open(filepath, 'rb'), mimetypes.guess_type(filepath)[0])}
269
+ else:
270
+ raise BadRequest(f"Provided content not found {filepath}")
271
+ response = self.put(content_link.uri, files=files)
272
+ self._validate_non_json_response(response=response)
273
+ return content_link
274
+
275
+ def fetch_content_attribute(self, content_link : HALLink) -> tuple[str, bytes]:
276
+ response = self.get(content_link.uri, headers={"Accept" : "*/*"})
277
+ self._validate_non_json_response(response=response)
278
+ content_disposition = response.headers.get('content-disposition')
279
+
280
+ if content_disposition and content_disposition != "attachment":
281
+ filename = decode_header(content_disposition)[1][0].decode("utf-8")
282
+ else:
283
+ # If content-disposition header is not present, try to extract filename from URL
284
+ filename = os.path.basename(content_link.name)
285
+ return (filename, response.content)
286
+
287
+ def fetch_all_content_attributes_from_entity_link(self, entity_link: HALLink) -> List[tuple[str, bytes]]:
288
+ return self.get_entity_instance(entity_link=entity_link).fetch_all_content_attributes()
289
+
290
+ def delete_link(self, link: HALLink) -> requests.Response:
291
+ response = self.delete(link.uri)
292
+ self._validate_non_json_response(response)
293
+ return response
294
+
295
+ def _validate_params(self, create_form : dict, attributes : dict, check_requirements : bool = True) -> None | Exception:
296
+ # Type checking
297
+ for attribute, value in attributes.items():
298
+ attribute_specs = list(filter(lambda spec : spec["name"] == attribute, create_form["properties"]))
299
+ if len(attribute_specs) == 1:
300
+ attribute_spec = attribute_specs[0]
301
+ multi_valued = False
302
+ if attribute_spec.get("options"):
303
+ if "minItems" in attribute_spec["options"].keys() and attribute_spec["options"]["minItems"] == 0:
304
+ if "maxItems" in attribute_spec["options"].keys() and attribute_spec["options"]["maxItems"] == 1:
305
+ multi_valued = False
306
+ else:
307
+ multi_valued = True
308
+ if multi_valued:
309
+ if type(value) == list:
310
+ for v in value:
311
+ if not hal_form_type_check[attribute_spec["type"]](v):
312
+ raise IncorrectAttributeType(f"Attribute {attribute} has an incorrect type {type(value)} in its list value. {v} is not of type {hal_form_types[attribute_spec['type']]}")
313
+ else:
314
+ raise IncorrectAttributeType(f"Attribute {attribute} has an incorrect type {type(value)}. Should be of type list")
315
+ else:
316
+ if not hal_form_type_check[attribute_spec["type"]](value):
317
+ raise IncorrectAttributeType(f"Attribute {attribute} has an incorrect type {type(value)}. Should be of type {hal_form_types[attribute_spec['type']]}")
318
+ else:
319
+ logging.warning(f"Attribute {attribute} does not in exist in the entity hal forms specification")
320
+ #raise NonExistantAttribute(f"Attribute {attribute} does not in exist in the entity specification")
321
+
322
+ if check_requirements:
323
+ # Check for required properties is not needed when patching an entity
324
+ for attribute_spec in create_form["properties"]:
325
+ if attribute_spec.get("required"):
326
+ if not attribute_spec["name"] in attributes.keys():
327
+ raise MissingRequiredAttribute(f"Required attribute {attribute_spec['name']} not present in payload.")
328
+
@@ -0,0 +1,95 @@
1
+ Metadata-Version: 2.1
2
+ Name: contentgrid-application-client
3
+ Version: 0.0.1
4
+ Summary: Python Client for interacting with ContentGrid Applications
5
+ Author-email: Ranec Belpaire <ranec.belpaire@xenit.eu>
6
+ License: Copyright 2024 Xenit Solutions
7
+
8
+ Licensed under the Apache License, Version 2.0 (the "License");
9
+ you may not use this file except in compliance with the License.
10
+ You may obtain a copy of the License at
11
+
12
+ http://www.apache.org/licenses/LICENSE-2.0
13
+
14
+ Unless required by applicable law or agreed to in writing, software
15
+ distributed under the License is distributed on an "AS IS" BASIS,
16
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ See the License for the specific language governing permissions and
18
+ limitations under the License.
19
+ Classifier: Development Status :: 3 - Alpha
20
+ Classifier: Operating System :: OS Independent
21
+ Classifier: Programming Language :: Python :: 3
22
+ Requires-Python: >=3.5
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: contentgrid-hal-client==0.0.1
26
+
27
+ # ContentGrid Python Client
28
+
29
+ This ContentGrid Client is a Python library designed to interact with ContentGrid API endpoints, specifically in the HAL response type. For ContentGrid Applications, it provides a convenient interface for performing various operations such as fetching profiles, creating entities, fetching related entities, handling content attributes, and more.
30
+
31
+ ## Features
32
+
33
+ - **Profile Handling**: Fetch profiles to retrieve HAL-forms specification about available entities and their attributes.
34
+ - **Entity Operations**: Create, fetch, update, and delete entities using a simple functions without needing to worry about headers and authentication.
35
+ - **Content Handling**: Upload and download content on content-attributes of entities.
36
+ - **Error Handling**: Provides basic error handling for network requests.
37
+ - **Attribute validation**: Provides attribute type checks for creating and updating entities. Checks wether attributes have the correct type and if all required attributes are present.
38
+
39
+ ## Installation
40
+
41
+ To install the ContentGrid API Client, you can use pip:
42
+
43
+ ```bash
44
+ pip install contentgrid-application-client
45
+ ```
46
+
47
+ ## Usage
48
+
49
+ ### ContentGridApplicationClient
50
+ ```python
51
+ from contentgrid-application-client import ContentGridApplicationClient
52
+
53
+ # Initialize the client with service account
54
+ client = ContentGridApplicationClient(
55
+ client_endpoint="https://b93ccecf-3466-44c0-995e-2620a8c66ac3.eu-west-1.contentgrid.cloud",
56
+ auth_uri="https://auth.eu-west-1.contentgrid.cloud/realms/cg-eade54da-3903-4554-aa5e-2982cd4126f1/protocol/openid-connect/token",
57
+ client_id="your_client_id",
58
+ client_secret="your_client_secret"
59
+ )
60
+
61
+ # Initialize the client with token
62
+ client = ContentGridApplicationClient(
63
+ client_endpoint="https://b93ccecf-3466-44c0-995e-2620a8c66ac3.eu-west-1.contentgrid.cloud",
64
+ token="your_token"
65
+ )
66
+
67
+ # Fetch profile
68
+ profile = client.get_profile()
69
+
70
+ # Create entity
71
+ attributes = {
72
+ "name": "Example Entity",
73
+ "description": "This is an example entity"
74
+ }
75
+ entity = client.create_entity("entity-name", attributes)
76
+ ```
77
+
78
+ ## Testing
79
+ Installing requirements:
80
+ ```bash
81
+ pip install -r requirements.txt
82
+ ```
83
+
84
+ Running tests:
85
+
86
+ ```bash
87
+ python -m pytest
88
+ ```
89
+
90
+ Running tests with coverage:
91
+
92
+ ```bash
93
+ coverage run -m pytest && coverage report -m
94
+ ```
95
+
@@ -0,0 +1,24 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ pytest.ini
5
+ requirements.txt
6
+ setup.py
7
+ src/contentgrid_application_client/__init__.py
8
+ src/contentgrid_application_client/application.py
9
+ src/contentgrid_application_client.egg-info/PKG-INFO
10
+ src/contentgrid_application_client.egg-info/SOURCES.txt
11
+ src/contentgrid_application_client.egg-info/dependency_links.txt
12
+ src/contentgrid_application_client.egg-info/requires.txt
13
+ src/contentgrid_application_client.egg-info/top_level.txt
14
+ tests/fixtures.py
15
+ tests/test_collection.py
16
+ tests/test_content.py
17
+ tests/test_curies.py
18
+ tests/test_entity_creation.py
19
+ tests/test_hal_form_type_check.py
20
+ tests/test_openapi.py
21
+ tests/test_profile.py
22
+ tests/test_relations.py
23
+ tests/example-docs/document.jpg
24
+ tests/example-docs/resume.pdf
@@ -0,0 +1,34 @@
1
+ from pytest import fixture
2
+ import os
3
+ from dotenv import load_dotenv
4
+
5
+ from contentgrid_application_client import ContentGridApplicationClient
6
+
7
+ load_dotenv(override=True)
8
+ load_dotenv(".env.secret", override=True)
9
+
10
+ CONTENTGRID_CLIENT_ENDPOINT = os.getenv("CONTENTGRID_CLIENT_ENDPOINT")
11
+ CONTENTGRID_AUTH_URI = os.getenv("CONTENTGRID_AUTH_URI")
12
+
13
+ # Service account
14
+ CONTENTGRID_CLIENT_ID = os.getenv("CONTENTGRID_CLIENT_ID")
15
+ CONTENTGRID_CLIENT_SECRET = os.getenv("CONTENTGRID_CLIENT_SECRET")
16
+
17
+ contentgrid_client = ContentGridApplicationClient(
18
+ client_endpoint=CONTENTGRID_CLIENT_ENDPOINT,
19
+ auth_uri=CONTENTGRID_AUTH_URI,
20
+ client_id=CONTENTGRID_CLIENT_ID,
21
+ client_secret=CONTENTGRID_CLIENT_SECRET,
22
+ )
23
+
24
+ @fixture
25
+ def cg_client() -> ContentGridApplicationClient:
26
+ return contentgrid_client
27
+
28
+ @fixture
29
+ def pdf_file_path() -> str:
30
+ return "contentgrid_application_client/tests/example-docs/resume.pdf"
31
+
32
+ @fixture
33
+ def img_file_path() -> str:
34
+ return "contentgrid_application_client/tests/example-docs/document.jpg"
@@ -0,0 +1,36 @@
1
+ from contentgrid_application_client import ContentGridApplicationClient, EntityCollection, EntityObject
2
+ from fixtures import cg_client
3
+
4
+ def test_get_collection(cg_client: ContentGridApplicationClient):
5
+ profile = cg_client.get_profile()
6
+ entity_name = profile.get_entity_links()[0].name
7
+ collection_response = cg_client.get_entity_collection(entity_name)
8
+
9
+ assert type(collection_response) == EntityCollection
10
+ assert collection_response.page_info != None
11
+ assert collection_response.page_info.total_elements >= 0
12
+ assert collection_response.embedded != None
13
+ assert collection_response.links != None
14
+
15
+
16
+ def test_get_entity(cg_client: ContentGridApplicationClient):
17
+ collection_response = cg_client.get_entity_collection(plural_entity_name="skills")
18
+
19
+ if collection_response.page_info.total_elements > 0:
20
+ for hal_object in collection_response.get_entities():
21
+ assert type(hal_object) == EntityObject
22
+ assert hal_object.id != None
23
+
24
+ example_entity_link = collection_response.get_entities()[0].get_self_link()
25
+ entity_object = cg_client.get_entity_instance(entity_link=example_entity_link)
26
+
27
+ assert type(entity_object) == EntityObject
28
+ assert entity_object.id != None
29
+ assert len(entity_object.metadata.keys()) > 0
30
+
31
+ if collection_response.page_info.total_pages > 0:
32
+ collection_response.first()
33
+ collection_response.next()
34
+ collection_response.prev()
35
+ collection_response.last()
36
+ assert type(collection_response) == EntityCollection
@@ -0,0 +1,94 @@
1
+ from contentgrid_application_client import ContentGridApplicationClient
2
+ from contentgrid_hal_client.exceptions import BadRequest, NotFound
3
+ from fixtures import cg_client, pdf_file_path, img_file_path
4
+ import logging
5
+ import os
6
+
7
+
8
+ def test_add_content(
9
+ cg_client: ContentGridApplicationClient, pdf_file_path: str, img_file_path: str
10
+ ):
11
+ os.makedirs("output", exist_ok=True)
12
+ entity_name = "candidates"
13
+ logging.info("Creating test entity...")
14
+ entity = cg_client.create_entity(entity_name, attributes={"name": "test"})
15
+
16
+ has_failed = False
17
+ try:
18
+ cg_client.put_content_attribute(
19
+ entity_link=entity.get_self_link(),
20
+ content_attribute_name="test",
21
+ filepath=pdf_file_path,
22
+ )
23
+ except NotFound as e:
24
+ has_failed = True
25
+ assert has_failed
26
+
27
+ has_failed = False
28
+ try:
29
+ cg_client.put_content_attribute(
30
+ entity_link=entity.get_self_link(),
31
+ content_attribute_name="content",
32
+ filepath="test.pdf",
33
+ )
34
+ except BadRequest as e:
35
+ has_failed = True
36
+ assert has_failed
37
+
38
+ logging.info("Adding pdf content...")
39
+ contentlink = cg_client.put_content_attribute(
40
+ entity_link=entity.get_self_link(),
41
+ content_attribute_name="content",
42
+ filepath=pdf_file_path,
43
+ )
44
+ logging.info(contentlink.uri)
45
+ logging.info("validating that content exists")
46
+ filename, file = cg_client.fetch_content_attribute(
47
+ content_link=contentlink
48
+ )
49
+
50
+ filepath = os.path.join("output", filename)
51
+ with open(filepath, 'wb') as f:
52
+ f.write(file)
53
+
54
+ assert os.path.exists(filepath)
55
+
56
+ logging.info("Adding img content...")
57
+ contentlink = cg_client.put_on_content_link(
58
+ content_link=contentlink, filepath=img_file_path
59
+ )
60
+ logging.info(contentlink.uri)
61
+ logging.info("validating that content exists")
62
+ filename, file = cg_client.fetch_content_attribute(
63
+ content_link=contentlink
64
+ )
65
+
66
+ filepath = os.path.join("output", filename)
67
+ with open(filepath, 'wb') as f:
68
+ f.write(file)
69
+
70
+ assert os.path.exists(filepath)
71
+
72
+
73
+ def test_add_multiple_content(
74
+ cg_client: ContentGridApplicationClient, pdf_file_path: str, img_file_path: str
75
+ ):
76
+ entity_name = "candidates"
77
+ logging.info("creating test entity...")
78
+ entity = cg_client.create_entity(entity_name, attributes={"name": "test"})
79
+ logging.info("Adding pdf content...")
80
+ contentlink = cg_client.put_content_attribute(
81
+ entity_link=entity.get_self_link(),
82
+ content_attribute_name="content",
83
+ filepath=pdf_file_path,
84
+ )
85
+ imglink = cg_client.put_content_attribute(
86
+ entity_link=entity.get_self_link(),
87
+ content_attribute_name="json_annotations",
88
+ filepath=img_file_path,
89
+ )
90
+ content_file_paths = cg_client.fetch_all_content_attributes_from_entity_link(entity_link=entity.get_self_link())
91
+ assert len(content_file_paths) == 2
92
+ cg_client.delete_link(entity.get_self_link())
93
+
94
+
@@ -0,0 +1,18 @@
1
+ from typing import List
2
+ from contentgrid_application_client import ContentGridApplicationClient
3
+ from contentgrid_hal_client import HALLink
4
+ from fixtures import cg_client
5
+
6
+
7
+ def test_curies(cg_client:ContentGridApplicationClient):
8
+ entity_link_relation = "https://contentgrid.cloud/rels/contentgrid/entity"
9
+ profile = cg_client.get_profile()
10
+
11
+ entity_links : List[HALLink] = profile.get_entity_links()
12
+
13
+ assert len(entity_links) > 0
14
+ for entity_link in entity_links:
15
+ assert entity_link.link_relation == entity_link_relation
16
+
17
+ assert "cg:entity" == profile.curie_registry.compact_curie(entity_link_relation)
18
+ assert profile.curie_registry.expand_curie("cg:entity") == entity_link_relation
@@ -0,0 +1,129 @@
1
+ from contentgrid_application_client import (
2
+ ContentGridApplicationClient
3
+ )
4
+ from contentgrid_hal_client.exceptions import IncorrectAttributeType, MissingRequiredAttribute
5
+ from fixtures import cg_client
6
+ import logging
7
+
8
+ default_values = {
9
+ "text": "test",
10
+ "datetime": "2024-03-18T15:25:54.838Z",
11
+ "checkbox": False,
12
+ "number": 10,
13
+ }
14
+
15
+ def test_create_update_entity(cg_client: ContentGridApplicationClient):
16
+ entity_name = "candidates"
17
+ text_attribute = "name"
18
+
19
+ candidates_profile = cg_client.get_entity_profile(entity_name)
20
+
21
+ attributes = {}
22
+
23
+ for property in candidates_profile.templates["create-form"]["properties"]:
24
+ if property["type"] in default_values.keys():
25
+ attributes[property["name"]] = default_values[property["type"]]
26
+
27
+ logging.info("Creating entity...")
28
+ entity = cg_client.create_entity(entity_name, attributes=attributes)
29
+ logging.info("Entity created.")
30
+
31
+ assert entity.metadata != None
32
+ assert entity.id != None
33
+ assert entity.get_self_link() != None
34
+
35
+ attributes[text_attribute] = "test-2"
36
+ logging.info("Updating attributes with PUT")
37
+ updated_entity = cg_client.put_entity_attributes(
38
+ entity_link=entity.get_self_link(), attributes=attributes
39
+ )
40
+ logging.info("Updating attributes with PATCH")
41
+ patched_entity = cg_client.patch_entity_attributes(
42
+ entity_link=updated_entity.get_self_link(),
43
+ attributes={text_attribute: "test-3"},
44
+ )
45
+ logging.info("done.")
46
+ assert entity.metadata[text_attribute] == "test"
47
+ assert updated_entity.metadata[text_attribute] == "test-2"
48
+ assert patched_entity.metadata[text_attribute] == "test-3"
49
+
50
+ logging.info("Deleting entity...")
51
+ cg_client.delete_link(entity.get_self_link())
52
+
53
+ entity_deleted = False
54
+ try:
55
+ cg_client.get_entity_instance(entity_link=entity.get_self_link())
56
+ except Exception as e:
57
+ logging.error(str(e))
58
+ entity_deleted = True
59
+
60
+ logging.info("Checking if entity is deleted")
61
+ assert entity_deleted
62
+
63
+
64
+ def test_create_entity_required_relationship(cg_client: ContentGridApplicationClient):
65
+ entity_name = "anonymous-candidates"
66
+
67
+ test_candidate = cg_client.get_entity_collection(
68
+ plural_entity_name="candidates"
69
+ ).get_entities()[0]
70
+
71
+ attributes = {
72
+ "anonymized_name": "test-candidate",
73
+ "original": test_candidate.get_self_link(),
74
+ }
75
+
76
+ test_anon_candidate = cg_client.create_entity(entity_name, attributes=attributes)
77
+ cg_client.delete_link(test_anon_candidate.get_self_link())
78
+
79
+
80
+ def test_create_entity_incorrect_params(cg_client: ContentGridApplicationClient):
81
+ entity_name = "anonymous-candidates"
82
+
83
+ threw_error = False
84
+ try:
85
+ cg_client.create_entity(entity_name, {"anonymized_name": 123})
86
+ except IncorrectAttributeType as e:
87
+ threw_error = True
88
+ except MissingRequiredAttribute as e:
89
+ threw_error = True
90
+
91
+ assert threw_error
92
+
93
+ test_candidate = cg_client.get_entity_collection(
94
+ plural_entity_name="candidates"
95
+ ).get_entities()[0]
96
+
97
+ threw_error = False
98
+ try:
99
+ entity = cg_client.create_entity(
100
+ entity_name,
101
+ {
102
+ "anonymized_name": 123,
103
+ "original": test_candidate.get_self_link(),
104
+ },
105
+ )
106
+ except IncorrectAttributeType as e:
107
+ threw_error = True
108
+ except MissingRequiredAttribute as e:
109
+ threw_error = True
110
+
111
+ assert threw_error
112
+
113
+ threw_error = False
114
+ try:
115
+ entity = cg_client.create_entity(
116
+ entity_name,
117
+ {
118
+ "anonymized_name": "test-candidate",
119
+ "original": test_candidate.get_self_link(),
120
+ },
121
+ )
122
+ except IncorrectAttributeType as e:
123
+ threw_error = True
124
+ except MissingRequiredAttribute as e:
125
+ threw_error = True
126
+
127
+ assert not threw_error
128
+
129
+ cg_client.delete_link(entity.get_self_link())
@@ -0,0 +1,20 @@
1
+ from contentgrid_application_client import hal_form_type_check
2
+
3
+
4
+ def test_hal_from_type_check():
5
+ number = 12
6
+ boolean = True
7
+ string = "test"
8
+ url = "https://b93ccecf-3466-44c0-995e-2620a8c66ac3.eu-west-1.contentgrid.cloud/skills/b4d84ca3-b436-4522-a1c3-71aaaf6f73ce"
9
+ datetime = "2024-03-20T16:48:59.904Z"
10
+
11
+ assert hal_form_type_check["text"](string)
12
+ assert hal_form_type_check["datetime"](datetime)
13
+ assert hal_form_type_check["checkbox"](boolean)
14
+ assert hal_form_type_check["number"](number)
15
+ assert hal_form_type_check["url"](url)
16
+
17
+ assert not hal_form_type_check["text"](number)
18
+ assert not hal_form_type_check["number"](string)
19
+ assert not hal_form_type_check["checkbox"]("true")
20
+ assert not hal_form_type_check["datetime"]("2024-03-20")
@@ -0,0 +1,12 @@
1
+ from contentgrid_application_client import ContentGridApplicationClient
2
+ from fixtures import cg_client
3
+ import os
4
+
5
+
6
+ def test_fetch_openapi_spec(cg_client: ContentGridApplicationClient):
7
+ filename, file = cg_client.fetch_openapi_yaml()
8
+ os.makedirs("output", exist_ok=True)
9
+ output_path = os.path.join("output", filename)
10
+ with open(output_path, 'wb') as f:
11
+ f.write(file)
12
+ assert os.path.exists(output_path)
@@ -0,0 +1,24 @@
1
+ from contentgrid_application_client import ContentGridApplicationClient, Profile
2
+ from contentgrid_hal_client.exceptions import NotFound
3
+ from fixtures import cg_client
4
+
5
+
6
+ def test_get_profile(cg_client: ContentGridApplicationClient):
7
+ profile = cg_client.get_profile()
8
+ assert type(profile) == Profile
9
+ assert len(profile.get_entity_links()) > 0
10
+
11
+
12
+ def test_get_specific_profile(cg_client: ContentGridApplicationClient):
13
+ profile = cg_client.get_profile()
14
+ entity_profile = cg_client.get_entity_profile(
15
+ profile.get_entity_links()[0].name
16
+ )
17
+ assert entity_profile.templates != None
18
+
19
+ has_failed = False
20
+ try:
21
+ cg_client.get_entity_profile("fadlfjaklsdjfhkladsf")
22
+ except NotFound as e:
23
+ has_failed = True
24
+ assert has_failed
@@ -0,0 +1,54 @@
1
+ from contentgrid_application_client import ContentGridApplicationClient, EntityCollection
2
+ from fixtures import cg_client
3
+ import logging
4
+
5
+ def test_relations(cg_client: ContentGridApplicationClient):
6
+ entity_name = "candidates"
7
+ relation_name = "skills"
8
+
9
+ entity = cg_client.create_entity(entity_name, {"name": "test"})
10
+
11
+ # fetch related skills
12
+ related_skills = cg_client.get_entity_relation_collection(
13
+ entity.get_self_link(), relation_name
14
+ )
15
+
16
+ assert type(related_skills) == EntityCollection
17
+
18
+ # entity just instantiated so no skills
19
+ assert len(related_skills.get_entities()) == 0
20
+
21
+ skills = cg_client.get_entity_collection("skills")
22
+
23
+ amt_skills_to_sample = 4
24
+ skills_selection = skills.get_entities()[:amt_skills_to_sample]
25
+ assert len(skills_selection) == amt_skills_to_sample
26
+
27
+ skills_selection[1] = skills_selection[1].get_self_link().uri
28
+ skills_selection[2] = skills_selection[2].get_self_link()
29
+
30
+ cg_client.put_entity_relation(
31
+ entity.get_self_link(),
32
+ relation_name=relation_name,
33
+ related_entity_links=skills_selection[:-1],
34
+ )
35
+
36
+ related_skills = cg_client.get_entity_relation_collection(
37
+ entity_link=entity.get_self_link(), relation_name=relation_name
38
+ )
39
+
40
+ assert len(related_skills.get_entities()) == amt_skills_to_sample - 1
41
+
42
+ # post last skill
43
+ cg_client.post_entity_relation(
44
+ entity_link=entity.get_self_link(),
45
+ relation_name=relation_name,
46
+ related_entity_links=[skills_selection[-1]],
47
+ )
48
+
49
+ related_skills = cg_client.get_entity_relation_collection(
50
+ entity_link=entity.get_self_link(), relation_name=relation_name
51
+ )
52
+ assert len(related_skills.get_entities()) == amt_skills_to_sample
53
+
54
+ cg_client.delete_link(entity.get_self_link())