Skip to content

Commit a7bbdf9

Browse files
committed
KAB-46 add configuration metadata create, list and delete
1 parent 50f429f commit a7bbdf9

3 files changed

Lines changed: 170 additions & 0 deletions

File tree

kbcstorage/configurations.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"""
66
import json
77
from kbcstorage.base import Endpoint
8+
from kbcstorage.configurations_metadata import ConfigurationsMetadata
89

910

1011
class Configurations(Endpoint):
@@ -22,6 +23,7 @@ def __init__(self, root_url, token, branch_id):
2223
branch_id (str): The ID of branch to use, use 'default' to work without branch (in main).
2324
"""
2425
super().__init__(root_url, f"branch/{branch_id}/components", token)
26+
self.metadata = ConfigurationsMetadata(root_url, token, branch_id)
2527

2628
def detail(self, component_id, configuration_id):
2729
"""
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
"""
2+
Manages calls to the Storage API relating to configurations
3+
4+
Full documentation https://keboola.docs.apiary.io/#reference/components-and-configurations
5+
"""
6+
import json
7+
from kbcstorage.base import Endpoint
8+
9+
10+
class ConfigurationsMetadata(Endpoint):
11+
"""
12+
Configurations Endpoint
13+
"""
14+
15+
def __init__(self, root_url, token, branch_id):
16+
"""
17+
Create a Component endpoint.
18+
19+
Args:
20+
root_url (:obj:`str`): The base url for the API.
21+
token (:obj:`str`): A storage API key.
22+
branch_id (str): The ID of branch to use, use 'default' to work without branch (in main).
23+
"""
24+
super().__init__(root_url, f"branch/{branch_id}/components", token)
25+
26+
def detail(self, component_id, configuration_id):
27+
"""
28+
Retrieves information about a given configuration.
29+
30+
Args:
31+
component_id (str): The id of the component.
32+
configuration_id (str): The id of the configuration.
33+
34+
Returns:
35+
response_body: The parsed json from the HTTP response.
36+
37+
Raises:
38+
requests.HTTPError: If the API request fails.
39+
"""
40+
if not isinstance(component_id, str) or component_id == '':
41+
raise ValueError("Invalid component_id '{}'.".format(component_id))
42+
if not isinstance(configuration_id, str) or configuration_id == '':
43+
raise ValueError("Invalid component_id '{}'.".format(configuration_id))
44+
url = '{}/{}/configs/{}'.format(self.base_url, component_id, configuration_id)
45+
return self._get(url)
46+
47+
def delete(self, component_id, configuration_id, metadata_id):
48+
"""
49+
Deletes the configuration.
50+
51+
Args:
52+
component_id (str): The id of the component.
53+
configuration_id (str): The id of the configuration.
54+
55+
Raises:
56+
requests.HTTPError: If the API request fails.
57+
"""
58+
if not isinstance(component_id, str) or component_id == '':
59+
raise ValueError("Invalid component_id '{}'.".format(component_id))
60+
if not isinstance(configuration_id, str) or configuration_id == '':
61+
raise ValueError("Invalid component_id '{}'.".format(configuration_id))
62+
url = '{}/{}/configs/{}/metadata/{}'.format(self.base_url, component_id, configuration_id, metadata_id)
63+
self._delete(url)
64+
65+
def list(self, component_id, configuration_id):
66+
"""
67+
Lists configurations of the given component.
68+
69+
Args:
70+
component_id (str): The id of the component.
71+
72+
Raises:
73+
requests.HTTPError: If the API request fails.
74+
"""
75+
if not isinstance(component_id, str) or component_id == '':
76+
raise ValueError("Invalid component_id '{}'.".format(component_id))
77+
url = '{}/{}/configs/{}/metadata'.format(self.base_url, component_id, configuration_id)
78+
return self._get(url)
79+
80+
def create(self, component_id, configuration_id, provider, metadata):
81+
"""
82+
Create a new configuration.
83+
84+
Args:
85+
component_id (str): The id of the component.
86+
configuration (str): The id of the configuration.
87+
provider (str): The provider of the configuration (currently ignored and "user" is sent).
88+
key (str): The key of the configuration.
89+
value (str): The value of the configuration.
90+
Returns:
91+
response_body: The parsed json from the HTTP response.
92+
93+
Raises:
94+
requests.HTTPError: If the API request fails.
95+
"""
96+
if not isinstance(component_id, str) or component_id == '':
97+
raise ValueError("Invalid component_id '{}'.".format(component_id))
98+
if not isinstance(configuration_id, str) or configuration_id == '':
99+
raise ValueError("Invalid component_id '{}'.".format(configuration_id))
100+
url = '{}/{}/configs/{}/metadata'.format(self.base_url, component_id, configuration_id)
101+
if not isinstance(metadata, list):
102+
raise ValueError("Metadata must be a list '{}'.".format(metadata))
103+
for metadataItem in metadata:
104+
if not isinstance(metadataItem, dict):
105+
raise ValueError("Metadata item must be a dictionary '{}'.".format(metadataItem))
106+
107+
headers = {
108+
'Content-Type': 'application/json',
109+
'X-StorageApi-Token': self.token
110+
}
111+
data = {
112+
# 'provider': provider, # not yet implemented
113+
'metadata': metadata
114+
}
115+
return self._post(url, data=json.dumps(data), headers=headers)

tests/functional/test_configurations.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,56 @@ def testListConfigurations(self):
5050
with self.subTest():
5151
with self.assertRaises(exceptions.HTTPError):
5252
configurations = self.configurations.list('non-existent-component')
53+
54+
def testConfigurationMetadata(self):
55+
self.configurations.create(
56+
component_id=self.TEST_COMPONENT_NAME,
57+
configuration_id='test_configuration_metadata',
58+
name='test_configuration_metadata',
59+
)
60+
metadataPayload = [
61+
{
62+
'key': 'testConfigurationMetadata',
63+
'value': 'success',
64+
}
65+
]
66+
metadataList = self.configurations.metadata.create(
67+
component_id=self.TEST_COMPONENT_NAME,
68+
configuration_id='test_configuration_metadata',
69+
provider='test',
70+
metadata=metadataPayload,
71+
)
72+
73+
with (self.subTest('assert metadata create response')):
74+
self.assertEqual(1, len(metadataList))
75+
metadataItem = metadataList[0]
76+
self.assertTrue('id' in metadataItem)
77+
# self.assertTrue('provider' in metadata) not yet
78+
self.assertTrue('key' in metadataItem)
79+
self.assertTrue('value' in metadataItem)
80+
81+
metadataList = self.configurations.metadata.list(
82+
component_id=self.TEST_COMPONENT_NAME,
83+
configuration_id='test_configuration_metadata'
84+
)
85+
86+
with (self.subTest('assert metadata list response')):
87+
self.assertTrue(len(metadataList) > 0)
88+
for metadataList in metadataList:
89+
self.assertTrue('id' in metadataList)
90+
# self.assertTrue('provider' in metadata) not yet
91+
self.assertTrue('key' in metadataList)
92+
self.assertTrue('value' in metadataList)
93+
94+
self.configurations.metadata.delete(
95+
component_id=self.TEST_COMPONENT_NAME,
96+
configuration_id='test_configuration_metadata',
97+
metadata_id=metadataList['id']
98+
)
99+
metadataList = self.configurations.metadata.list(
100+
component_id=self.TEST_COMPONENT_NAME,
101+
configuration_id='test_configuration_metadata'
102+
)
103+
104+
with (self.subTest('assert metadata delete means metadata no longer in list')):
105+
self.assertTrue(len(metadataList) == 0)

0 commit comments

Comments
 (0)