Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions kbcstorage/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,6 @@ def __init__(self, root_url, path_component, token):
"""
if not root_url:
raise ValueError("Root URL is required.")
if not path_component:
raise ValueError("Path component is required.")
if not token:
raise ValueError("Token is required.")
self.root_url = root_url
Expand Down
43 changes: 43 additions & 0 deletions kbcstorage/branches.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""
Manages calls to the Storage API relating to development branches

Full documentation https://keboola.docs.apiary.io/#reference/development-branches

"""
from kbcstorage.base import Endpoint


class Branches(Endpoint):
"""
Tokens Endpoint
"""
def __init__(self, root_url, token):
"""
Create a Tokens endpoint.

Args:
root_url (:obj:`str`): The base url for the API.
token (:obj:`str`): A storage API key.
"""
# Branches have inconsistent endpoint naming - it's either dev-branches or branch, so it need to be resolved
# endpoint by endpoint.
super().__init__(root_url, "", token)

def metadata(self, branch_id="default"):
"""
Get branch metadata

Args:
branch_id (str): The id of the branch or "default" to get metadata for the main branch (production).

Returns:
response_body: The parsed json from the HTTP response.

Raises:
requests.HTTPError: If the API request fails.
"""
if not isinstance(branch_id, str) or branch_id == "":
raise ValueError(f"Invalid branch_id '{branch_id}'")
Comment thread
odinuv marked this conversation as resolved.

url = f"{self.base_url}branch/{branch_id}/metadata"
return self._get(url)
5 changes: 4 additions & 1 deletion kbcstorage/client.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
""""
Entry point for the Storage API client.
"""

from kbcstorage.branches import Branches
from kbcstorage.buckets import Buckets
from kbcstorage.components import Components
from kbcstorage.configurations import Configurations
from kbcstorage.tokens import Tokens
from kbcstorage.workspaces import Workspaces
from kbcstorage.jobs import Jobs
from kbcstorage.tables import Tables
Expand Down Expand Up @@ -37,6 +38,8 @@ def __init__(self, api_domain, token, branch_id='default'):
self.workspaces = Workspaces(self.root_url, self.token)
self.components = Components(self.root_url, self.token, self.branch_id)
self.configurations = Configurations(self.root_url, self.token, self.branch_id)
self.tokens = Tokens(self.root_url, self.token)
self.branches = Branches(self.root_url, self.token)

@property
def token(self):
Expand Down
35 changes: 35 additions & 0 deletions kbcstorage/tokens.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""
Manages calls to the Storage API relating to tokens

Full documentation https://keboola.docs.apiary.io/#reference/tokens-and-permissions/.

"""
from kbcstorage.base import Endpoint


class Tokens(Endpoint):
"""
Tokens Endpoint
"""
def __init__(self, root_url, token):
"""
Create a Tokens endpoint.

Args:
root_url (:obj:`str`): The base url for the API.
token (:obj:`str`): A storage API key.
"""
super().__init__(root_url, 'tokens', token)

def verify(self):
"""
Verify token.

Returns:
response_body: The parsed json from the HTTP response.

Raises:
requests.HTTPError: If the API request fails.
"""
url = '{}/verify'.format(self.base_url)
return self._get(url)
5 changes: 0 additions & 5 deletions tests/functional/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,6 @@ def test_missing_url(self):
with self.assertRaisesRegex(ValueError, "Root URL is required."):
Endpoint(None, '', None)

def test_missing_part(self):
with self.assertRaisesRegex(ValueError,
"Path component is required."):
Endpoint('https://connection.keboola.com/', '', None)

def test_missing_token(self):
with self.assertRaisesRegex(ValueError, "Token is required."):
Endpoint('https://connection.keboola.com/', 'tables', None)
13 changes: 13 additions & 0 deletions tests/functional/test_branches.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import os

from kbcstorage.branches import Branches
from tests.base_test_case import BaseTestCase


class TestEndpoint(BaseTestCase):
def setUp(self):
self.branches = Branches(os.getenv('KBC_TEST_API_URL'), os.getenv('KBC_TEST_TOKEN'))

def test_metadata(self):
metadata = self.branches.metadata('default')
self.assertTrue(isinstance(metadata, list))
16 changes: 16 additions & 0 deletions tests/functional/test_tokens.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import os
from kbcstorage.tokens import Tokens
from tests.base_test_case import BaseTestCase


class TestEndpoint(BaseTestCase):
def setUp(self):
self.tokens = Tokens(os.getenv('KBC_TEST_API_URL'),
os.getenv('KBC_TEST_TOKEN'))

def test_verify(self):
token_info = self.tokens.verify()
self.assertTrue('id' in token_info)
self.assertTrue('description' in token_info)
self.assertTrue('canManageBuckets' in token_info)
self.assertTrue('owner' in token_info)
8 changes: 8 additions & 0 deletions tests/mocks/branches_responses.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
branches_metadata_response = [
{
"id": "93670",
"key": "KBC.projectDescription",
"value": "Testing project one",
"timestamp": "2023-05-31T17:52:18+0200"
}
]
49 changes: 49 additions & 0 deletions tests/mocks/test_branches.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
"""
Test basic functionality of the Branches endpoint
"""
import unittest

import responses

from kbcstorage.branches import Branches
from tests.mocks.branches_responses import branches_metadata_response


class TestBranchesEndpointWithMocks(unittest.TestCase):
"""
Test the methods of a Branches endpoint instance with mock HTTP responses
"""
def setUp(self):
token = 'dummy_token'
base_url = 'https://connection.keboola.com/'
self.branches = Branches(base_url, token)

@responses.activate
def test_metadata_no_branch(self):
"""
Branches lists metadata correctly
"""
responses.add(
responses.Response(
method='GET',
url='https://connection.keboola.com/v2/storage/branch/default/metadata',
json=branches_metadata_response
)
)
branch_metadata = self.branches.metadata()
self.assertEqual(branches_metadata_response, branch_metadata)

@responses.activate
def test_metadata_some_branch(self):
"""
Branches lists metadata correctly
"""
responses.add(
responses.Response(
method='GET',
url='https://connection.keboola.com/v2/storage/branch/1234/metadata',
json=branches_metadata_response
)
)
branch_metadata = self.branches.metadata('1234')
self.assertEqual(branches_metadata_response, branch_metadata)
34 changes: 34 additions & 0 deletions tests/mocks/test_tokens.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""
Test basic functionality of the Tokens endpoint
"""
import unittest

import responses

from kbcstorage.tokens import Tokens
from tests.mocks.token_responses import verify_token_response


class TestTokensEndpointWithMocks(unittest.TestCase):
"""
Test the methods of a Tokens endpoint instance with mock HTTP responses
"""
def setUp(self):
token = 'dummy_token'
base_url = 'https://connection.keboola.com/'
self.tokens = Tokens(base_url, token)

@responses.activate
def test_verify(self):
"""
Verify token returns correctly
"""
responses.add(
responses.Response(
method='GET',
url='https://connection.keboola.com/v2/storage/tokens/verify',
json=verify_token_response
)
)
token_info = self.tokens.verify()
self.assertEqual(verify_token_response, token_info)
141 changes: 141 additions & 0 deletions tests/mocks/token_responses.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
verify_token_response = {
"id": "373115",
"created": "2021-06-23T09:33:36+0200",
"refreshed": "2021-06-23T09:33:36+0200",
"description": "[email protected]",
"uri": "https://connection.keboola.com/v2/storage/tokens/373115",
"isMasterToken": True,
"canManageBuckets": True,
"canManageTokens": True,
"canReadAllFileUploads": True,
"canPurgeTrash": True,
"expires": None,
"isExpired": False,
"isDisabled": False,
"dailyCapacity": 0,
"token": "8625-373115-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"bucketPermissions": {
"out.c-test2": "manage",
"in.c-test3": "manage",
"out.c-test": "manage",
"out.c-some-other": "manage"
},
"owner": {
"id": 8625,
"name": "Test Project",
"type": "production",
"region": "us-east-1",
"created": "2021-06-23T09:33:32+0200",
"expires": None,
"features": [
"syrup-jobs-limit-10",
"oauth-v3",
"queuev2",
"storage-types",
"allow-ai",
"alternat",
],
"dataSizeBytes": 31474176,
"rowsCount": 797,
"hasMysql": False,
"hasSynapse": False,
"hasRedshift": True,
"hasSnowflake": True,
"hasExasol": False,
"hasTeradata": False,
"hasBigquery": False,
"defaultBackend": "snowflake",
"hasTryModeOn": "0",
"limits": {
"components.jobsParallelism": {
"name": "components.jobsParallelism",
"value": 10
},
"goodData.dataSizeBytes": {
"name": "goodData.dataSizeBytes",
"value": 1000000000
},
"goodData.demoTokenEnabled": {
"name": "goodData.demoTokenEnabled",
"value": 1
},
"goodData.prodTokenEnabled": {
"name": "goodData.prodTokenEnabled",
"value": 0
},
"goodData.usersCount": {
"name": "goodData.usersCount",
"value": 30
},
"kbc.adminsCount": {
"name": "kbc.adminsCount",
"value": 10
},
"kbc.extractorsCount": {
"name": "kbc.extractorsCount",
"value": 0
},
"kbc.monthlyProjectPowerLimit": {
"name": "kbc.monthlyProjectPowerLimit",
"value": 50
},
"kbc.writersCount": {
"name": "kbc.writersCount",
"value": 0
},
"orchestrations.count": {
"name": "orchestrations.count",
"value": 10
},
"storage.dataSizeBytes": {
"name": "storage.dataSizeBytes",
"value": 50000000000
},
"storage.jobsParallelism": {
"name": "storage.jobsParallelism",
"value": 10
}
},
"metrics": {
"kbc.adminsCount": {
"name": "kbc.adminsCount",
"value": 1
},
"orchestrations.count": {
"name": "orchestrations.count",
"value": 0
},
"storage.dataSizeBytes": {
"name": "storage.dataSizeBytes",
"value": 31474176
},
"storage.rowsCount": {
"name": "storage.rowsCount",
"value": 797
}
},
"isDisabled": False,
"billedMonthlyPrice": None,
"dataRetentionTimeInDays": 7,
"fileStorageProvider": "aws",
"redshift": {
"connectionId": 365,
"databaseName": "sapi_8625"
}
},
"organization": {
"id": "111111"
},
"admin": {
"name": "Devel",
"id": 59,
"features": [
"ui-devel-preview",
"manage-try-mode",
"validate-sql",
"early-adopter-preview"
],
"isOrganizationMember": True,
"role": "admin"
}
}