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.
- contentgrid_application_client-0.0.1/LICENSE +13 -0
- contentgrid_application_client-0.0.1/PKG-INFO +95 -0
- contentgrid_application_client-0.0.1/README.md +69 -0
- contentgrid_application_client-0.0.1/pyproject.toml +23 -0
- contentgrid_application_client-0.0.1/pytest.ini +5 -0
- contentgrid_application_client-0.0.1/requirements.txt +1 -0
- contentgrid_application_client-0.0.1/setup.cfg +4 -0
- contentgrid_application_client-0.0.1/setup.py +11 -0
- contentgrid_application_client-0.0.1/src/contentgrid_application_client/__init__.py +1 -0
- contentgrid_application_client-0.0.1/src/contentgrid_application_client/application.py +328 -0
- contentgrid_application_client-0.0.1/src/contentgrid_application_client.egg-info/PKG-INFO +95 -0
- contentgrid_application_client-0.0.1/src/contentgrid_application_client.egg-info/SOURCES.txt +24 -0
- contentgrid_application_client-0.0.1/src/contentgrid_application_client.egg-info/dependency_links.txt +1 -0
- contentgrid_application_client-0.0.1/src/contentgrid_application_client.egg-info/requires.txt +1 -0
- contentgrid_application_client-0.0.1/src/contentgrid_application_client.egg-info/top_level.txt +1 -0
- contentgrid_application_client-0.0.1/tests/example-docs/document.jpg +0 -0
- contentgrid_application_client-0.0.1/tests/example-docs/resume.pdf +0 -0
- contentgrid_application_client-0.0.1/tests/fixtures.py +34 -0
- contentgrid_application_client-0.0.1/tests/test_collection.py +36 -0
- contentgrid_application_client-0.0.1/tests/test_content.py +94 -0
- contentgrid_application_client-0.0.1/tests/test_curies.py +18 -0
- contentgrid_application_client-0.0.1/tests/test_entity_creation.py +129 -0
- contentgrid_application_client-0.0.1/tests/test_hal_form_type_check.py +20 -0
- contentgrid_application_client-0.0.1/tests/test_openapi.py +12 -0
- contentgrid_application_client-0.0.1/tests/test_profile.py +24 -0
- 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 @@
|
|
|
1
|
+
-e ../contentgrid_hal_client
|
|
@@ -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 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
contentgrid-hal-client==0.0.1
|
contentgrid_application_client-0.0.1/src/contentgrid_application_client.egg-info/top_level.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
contentgrid_application_client
|
|
Binary file
|
|
Binary file
|
|
@@ -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())
|