Skip to content

Commit 87b4835

Browse files
Added extension class implementation and test cases (#41) (#42)
* Added extension class implementation and test cases * Added required package * Added demo.html file for extension upload method * Updated ContentstackRegion to Region
1 parent 54119d1 commit 87b4835

File tree

19 files changed

+725
-11
lines changed

19 files changed

+725
-11
lines changed

.talismanrc

+12
Original file line numberDiff line numberDiff line change
@@ -312,4 +312,16 @@ fileignoreconfig:
312312
- filename: tests/unit/publish_queue/test_publish_queue_unit.py
313313
checksum: add55174c8c3ee4e931433e9bf54a80304d6927681912a19e5ee78747dc608bf
314314
version: ""
315+
fileignoreconfig:
316+
- filename: tests/api/extensions/test_extension_api.py
317+
checksum: 8e1dda663e6fd54d7a05c75ce7ed146b4c9dbf13103236f5bb45960482f32fef
318+
- filename: tests/resources/mock_extension/find.json
319+
checksum: ba433741506e21dd46a1ba72b81ff3ef6b9377c589cc90bdcab23b496762b630
320+
- filename: tests/resources/mock_extension/upload.json
321+
checksum: 20364016b0db0e64ad20a9a1ce922fdcfd6db3708f4046a129a54e3d22d80cec
322+
- filename: tests/unit/extensions/test_extension_unit.py
323+
checksum: 7d6c6cc7ca536a099d3c1e08a30fc9eeb441ca29b5b908b55bfb900bb27a181f
324+
- filename: tests/mock/extensions/test_extension_mock.py
325+
checksum: 88bf3b97761c880dc360e358018150e198117a2acef812d1f9ff4653708f8b5c
326+
version: ""
315327

contentstack_management/__init__.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from .auditlogs.auditlog import Auditlog
1717
from .environments.environment import Environment
1818
from .entries.entry import Entry
19-
from .contentstack import ContentstackClient, ContentstackRegion
19+
from .contentstack import ContentstackClient, Region
2020
from ._api_client import _APIClient
2121
from .common import Parameter
2222
from ._errors import ArgumentException
@@ -30,11 +30,12 @@
3030
from .delivery_token.delivery_token import DeliveryToken
3131
from .management_token.management_token import ManagementToken
3232
from .publish_queue.publish_queue import PublishQueue
33+
from .extensions.extension import Extension
3334

3435

3536
__all__ = (
3637
"ContentstackClient",
37-
"ContentstackRegion",
38+
"Region",
3839
"_APIClient",
3940
"Parameter",
4041
"ArgumentException",
@@ -63,7 +64,8 @@
6364
"ReleaseItems",
6465
"DeliveryToken",
6566
"ManagementToken",
66-
"PublishQueue"
67+
"PublishQueue",
68+
"Extension"
6769
)
6870

6971
__title__ = 'contentstack-management-python'

contentstack_management/contentstack.py

+7-5
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
version = '0.0.1'
1010

1111

12-
class ContentstackRegion(Enum):
12+
class Region(Enum):
1313
US = "us"
1414
EU = "eu"
1515
AZURE_EU = "azure-eu"
@@ -30,16 +30,18 @@ class ContentstackClient:
3030
# TODO: DefaultCSCredential(), needs to be implemented
3131
def __init__(self, host: str = 'api.contentstack.io', scheme: str = 'https://',
3232
authtoken=None, management_token=None, headers: dict = None,
33-
region: ContentstackRegion = ContentstackRegion.US, version='v3', timeout=2, max_retries: int = 18,
33+
region: Region = Region.US.value, version='v3', timeout=2, max_retries: int = 18,
3434
**kwargs):
3535
self.endpoint = 'https://api.contentstack.io/v3/'
36-
if region is not ContentstackRegion.US:
37-
self.endpoint = f'{scheme}{region.value}-{host}/{version}/'
38-
if host is not None:
36+
if region is not None and host is not None and region is not Region.US.value:
37+
self.endpoint = f'{scheme}{region}-{host}/{version}/'
38+
if host is not None and region is None:
3939
self.endpoint = f'{scheme}{host}/{version}/'
4040
if authtoken is not None:
4141
headers['authtoken'] = authtoken
4242

43+
44+
4345
if management_token is not None:
4446
headers['authorization'] = management_token
4547
headers = user_agents(headers)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from contentstack_management import contentstack
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
"""This class takes a base URL as an argument when it's initialized,
2+
which is the endpoint for the RESTFUL API that we'll be interacting with.
3+
The create(), read(), update(), and delete() methods each correspond to
4+
the CRUD operations that can be performed on the API """
5+
6+
import json
7+
from ..common import Parameter
8+
from urllib.parse import quote
9+
from .._errors import ArgumentException
10+
from requests_toolbelt.multipart.encoder import MultipartEncoder
11+
12+
class Extension(Parameter):
13+
"""
14+
This class takes a base URL as an argument when it's initialized,
15+
which is the endpoint for the RESTFUL API that
16+
we'll be interacting with. The create(), read(), update(), and delete()
17+
methods each correspond to the CRUD
18+
operations that can be performed on the API """
19+
20+
def __init__(self, client, extension_uid: str):
21+
self.client = client
22+
self.extension_uid = extension_uid
23+
super().__init__(self.client)
24+
25+
self.path = "extensions"
26+
27+
def find(self):
28+
"""
29+
The "Get all custom fields" request is used to get the information of all custom fields created in a stack.
30+
:return: Json, with extension details.
31+
-------------------------------
32+
[Example:]
33+
34+
>>> from contentstack_management import contentstack
35+
>>> client = contentstack.client(authtoken='your_authtoken')
36+
>>> result = client.stack("api_key").extension().find().json()
37+
-------------------------------
38+
"""
39+
return self.client.get(self.path, headers = self.client.headers)
40+
41+
42+
43+
def fetch(self):
44+
"""
45+
The "Fetch" request returns information about a specific extension.
46+
:return: Json, with extension details.
47+
-------------------------------
48+
[Example:]
49+
>>> from contentstack_management import contentstack
50+
>>> client = contentstack.client(authtoken='your_authtoken')
51+
>>> result = client.stack('api_key').extension('extension_uid').fetch().json()
52+
-------------------------------
53+
"""
54+
self.validate_uid()
55+
url = f"{self.path}/{self.extension_uid}"
56+
return self.client.get(url, headers = self.client.headers)
57+
58+
59+
def upload(self, data: dict):
60+
"""
61+
The Upload is used to upload a new custom widget, custom field, dashboard Widget to a stack.
62+
63+
:param data: The `data` parameter is the payload that you want to send in the request body. It
64+
should be a dictionary or a JSON serializable object that you want to send as the request body
65+
:return: Json, with extension details.
66+
-------------------------------
67+
[Example:]
68+
>>> extension = {
69+
>>> "file_name": "demo.html",
70+
>>> "file_path": "/Users/sunil.lakshman/Downloads/demo.html",
71+
>>> "data_type": 'text',
72+
>>> "title": 'Old Extension',
73+
>>> "multiple": False,
74+
>>> "tags": {},
75+
>>> "type": 'dashboard'
76+
>>> }
77+
>>> from contentstack_management import contentstack
78+
>>> client = contentstack.client(authtoken='your_authtoken')
79+
>>> result = client.stack('api_key').extension().upload(extension).json()
80+
-------------------------------
81+
"""
82+
83+
fields = {
84+
'extension[upload]': (f"{data['file_name']}", open(f"{data['file_name']}", 'rb'), 'text/html'),
85+
'extension[title]': f"{data['title']}",
86+
'extension[data_type]': f"{data['data_type']}",
87+
'extension[type]': f"{data['type']}",
88+
'extension[tags]': f"{data['tags']}",
89+
'extension[multiple]': f"{data['multiple']}"
90+
}
91+
content_type, body = self.encode_multipart_formdata(fields)
92+
self.client.headers['Content-Type'] = content_type
93+
return self.client.post(self.path, headers = self.client.headers, data = body)
94+
95+
def create(self, data: dict):
96+
"""
97+
The Create a extension call creates a new extension in a particular stack of your Contentstack account.
98+
99+
:param data: The `data` parameter is the data that you want to update. It should be a dictionary
100+
or an object that can be serialized to JSON
101+
:return: Json, with updated extension details.
102+
-------------------------------
103+
[Example:]
104+
>>> extension = {
105+
>>> tags: [
106+
>>> 'tag1',
107+
>>> 'tag2'
108+
>>> ],
109+
>>> data_type: 'text',
110+
>>> title: 'Old Extension',
111+
>>> src: "Enter either the source code (use 'srcdoc') or the external hosting link of the extension depending on the hosting method you selected.",
112+
>>> multiple: false,
113+
>>> config: {},
114+
>>> type: 'field'
115+
>>> }
116+
>>> from contentstack_management import contentstack
117+
>>> client = contentstack.client(authtoken='your_authtoken')
118+
>>> result = client.stack('api_key').extension("extension_uid").update(extension).json()
119+
-------------------------------
120+
"""
121+
data = json.dumps(data)
122+
return self.client.post(self.path, headers = self.client.headers, data=data)
123+
124+
def update(self, data: dict):
125+
"""
126+
The "Update Extensions call" will update the details of a custom field.
127+
128+
:param data: The `data` parameter is the data that you want to update. It should be a dictionary
129+
or an object that can be serialized to JSON
130+
:return: Json, with updated extension details.
131+
-------------------------------
132+
[Example:]
133+
>>> data = {
134+
>>> "extension": {
135+
>>> "tags": [
136+
>>> "tag1",
137+
>>> "tag2"
138+
>>> ],
139+
>>> "data_type": "text",
140+
>>> "title": "Old Extension",
141+
>>> "src": "Enter either the source code (use 'srcdoc') or the external hosting link of the extension depending on the hosting method you selected.",
142+
>>> "multiple": false,
143+
>>> "config": "{}",
144+
>>> "type": "field"
145+
>>> }
146+
>>> }
147+
>>> from contentstack_management import contentstack
148+
>>> client = contentstack.client(authtoken='your_authtoken')
149+
>>> result = client.stack('api_key').extension("extension_uid").update(data).json()
150+
-------------------------------
151+
"""
152+
self.validate_uid()
153+
url = f"{self.path}/{self.extension_uid}"
154+
data = json.dumps(data)
155+
return self.client.put(url, headers = self.client.headers, data=data)
156+
157+
158+
def delete(self):
159+
"""
160+
The "Delete custom field" request deletes a specific custom field.
161+
162+
:return: The delete() method returns the status code and message as a response.
163+
-------------------------------
164+
[Example:]
165+
>>> from contentstack_management import contentstack
166+
>>> client = contentstack.client(authtoken='your_authtoken')
167+
>>> result = client.stack('api_key').extension('extension_uid').delete().json()
168+
-------------------------------
169+
"""
170+
self.validate_uid()
171+
url = f"{self.path}/{self.extension_uid}"
172+
return self.client.delete(url, headers = self.client.headers)
173+
174+
def validate_uid(self):
175+
if self.extension_uid is None or '':
176+
raise ArgumentException("Extension Uid is required")
177+
178+
def encode_multipart_formdata(self, fields):
179+
# Create a MultipartEncoder instance with the specified fields
180+
encoder = MultipartEncoder(fields)
181+
# Set the content type to the encoder's content type
182+
content_type = encoder.content_type
183+
# Get the encoded body
184+
body = encoder.to_string()
185+
return content_type, body

contentstack_management/stack/stack.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from ..delivery_token.delivery_token import DeliveryToken
2121
from ..management_token.management_token import ManagementToken
2222
from ..publish_queue.publish_queue import PublishQueue
23+
from ..extensions.extension import Extension
2324

2425

2526
class Stack(Parameter):
@@ -371,4 +372,7 @@ def management_token(self, management_token: str = None):
371372
return ManagementToken(self.client, management_token)
372373

373374
def publish_queue(self, publish_queue_uid: str = None):
374-
return PublishQueue(self.client, publish_queue_uid)
375+
return PublishQueue(self.client, publish_queue_uid)
376+
377+
def extension(self, extension_uid: str = None):
378+
return Extension(self.client, extension_uid)

requirements.txt

+1
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ setuptools==68.0.0
33
requests~=2.31.0
44
pylint
55
bson>=0.5.9
6+
requests-toolbelt>=1.0.0

setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
"Programming Language :: Python :: 3.10",
2424
"Operating System :: OS Independent",
2525
],
26-
install_requires=["bson >= 0.5.9", "requests >= 2.5.4"],
26+
install_requires=["bson >= 0.5.9", "requests >= 2.5.4", "requests-toolbelt > = 1.0.0"],
2727
extras_require={
2828
"dev": ["pytest>=7.0", "twine>=4.0.2", "dotenv>=0.0.5"],
2929
},
+91
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import os
2+
import unittest
3+
from dotenv import load_dotenv
4+
from contentstack_management import contentstack
5+
from tests.cred import get_credentials
6+
7+
credentials = get_credentials()
8+
username = credentials["username"]
9+
password = credentials["password"]
10+
host = credentials["host"]
11+
api_key = credentials["api_key"]
12+
extension_uid = credentials["extension_uid"]
13+
14+
class extensionApiTests(unittest.TestCase):
15+
16+
def setUp(self):
17+
self.client = contentstack.ContentstackClient(host=host)
18+
self.client.login(username, password)
19+
20+
def test_get_all_extension(self):
21+
response = self.client.stack(api_key).extension().find()
22+
self.assertEqual(response.request.url, f"{self.client.endpoint}extensions")
23+
self.assertEqual(response.status_code, 200)
24+
25+
def test_get_a_extension(self):
26+
response = self.client.stack(api_key).extension(extension_uid).fetch()
27+
self.assertEqual(response.request.url,
28+
f"{self.client.endpoint}extensions/{extension_uid}")
29+
self.assertEqual(response.status_code, 200)
30+
31+
def test_create(self):
32+
extension = {
33+
"extension": {
34+
"tags": [
35+
"tag1",
36+
"tag2"
37+
],
38+
"data_type": "text",
39+
"title": "New Custom Field",
40+
"src": "https://www.sample.com",
41+
"multiple": False,
42+
"config": "{}",
43+
"type": "field"
44+
}
45+
}
46+
response = self.client.stack(api_key).extension().create(extension)
47+
self.assertEqual(response.request.url, f"{self.client.endpoint}extensions")
48+
self.assertEqual(response.status_code, 201)
49+
50+
def test_upload(self):
51+
extension = {
52+
"file_name": "demo.html",
53+
"file_path": "/Users/sunil.lakshman/Downloads/demo.html",
54+
"data_type": 'text',
55+
"title": 'Old Extension',
56+
"multiple": False,
57+
"tags": {},
58+
"type": 'dashboard'
59+
}
60+
61+
response = self.client.stack(api_key).extension().upload(extension)
62+
self.assertEqual(response.request.url, f"{self.client.endpoint}extensions")
63+
self.assertEqual(response.status_code, 201)
64+
65+
def test_update_extension(self):
66+
extension = {
67+
"extension": {
68+
"tags": [
69+
"tag1",
70+
"tag2"
71+
],
72+
"data_type": "text",
73+
"title": "New Custom Field",
74+
"src": "https://www.sample.com",
75+
"multiple": False,
76+
"config": "{}",
77+
"type": "field"
78+
}
79+
}
80+
response = self.client.stack(api_key).extension(extension_uid).update(extension)
81+
self.assertEqual(response.request.url,
82+
f"{self.client.endpoint}extensions/{extension_uid}")
83+
self.assertEqual(response.status_code, 200)
84+
85+
86+
def test_delete_extension(self):
87+
response = self.client.stack(api_key).extension(extension_uid).delete()
88+
self.assertEqual(response.request.url, f"{self.client.endpoint}extensions/{extension_uid}")
89+
self.assertEqual(response.status_code, 200)
90+
91+

0 commit comments

Comments
 (0)