Skip to content

Commit 4881877

Browse files
committed
added alternative blackduck client and alternative core methods based on new BearerAuth class
1 parent e205b47 commit 4881877

File tree

2 files changed

+244
-0
lines changed

2 files changed

+244
-0
lines changed

blackduck/Client.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
'''
2+
Created on Dec 23, 2020
3+
@author: ar-calder
4+
5+
Wrapper for common HUB API queries.
6+
Upon initialization Bearer token is obtained and used for all subsequent calls.
7+
Token will auto-renew on timeout.
8+
'''
9+
10+
from .Utils import find_field, safe_get
11+
from .Authentication import BearerAuth
12+
import logging
13+
import requests
14+
logger = logging.getLogger(__name__)
15+
16+
class Client:
17+
VERSION_DISTRIBUTION=["EXTERNAL", "SAAS", "INTERNAL", "OPENSOURCE"]
18+
VERSION_PHASES = ["PLANNING", "DEVELOPMENT", "PRERELEASE", "RELEASED", "DEPRECATED", "ARCHIVED"]
19+
PROJECT_VERSION_SETTINGS = ['nickname', 'releaseComments', 'versionName', 'phase', 'distribution', 'releasedOn']
20+
21+
from .Exceptions import(
22+
http_exception_handler
23+
)
24+
25+
from .ClientCore import (
26+
_request, _get_items, _get_resource_href, get_resource, list_resources, _get_base_resource_url, get_base_resource, _get_parameter_string
27+
)
28+
29+
def __init__(
30+
self,
31+
*args,
32+
token=None,
33+
base_url=None,
34+
session=None,
35+
auth=None,
36+
verify=True,
37+
timeout=15,
38+
**kwargs):
39+
40+
self.verify=verify
41+
self.timeout=int(timeout)
42+
self.base_url=base_url
43+
self.session = session or requests.session()
44+
self.auth = auth or BearerAuth(
45+
session = self.session,
46+
token=token,
47+
base_url=base_url,
48+
verify=self.verify
49+
)
50+
51+
def print_methods(self):
52+
import inspect
53+
for fn in inspect.getmembers(self, predicate=inspect.ismember):
54+
print(fn[0])
55+
56+
# Example for projects
57+
def get_projects(self, parameters=[], **kwargs):
58+
return self._get_items(
59+
method='GET',
60+
# url unlikely to change hence is_public=false (faster).
61+
url= self._get_base_resource_url('projects', is_public=False),
62+
name="project",
63+
**kwargs
64+
)
65+
66+
def get_project_by_name(self, project_name, **kwargs):
67+
projects = self.get_projects(**kwargs)
68+
return find_field(projects, 'name', project_name)

blackduck/ClientCore.py

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
'''
2+
Created on Dec 23, 2020
3+
@author: ar-calder
4+
5+
'''
6+
7+
import logging
8+
import requests
9+
import json
10+
11+
from .Utils import find_field, safe_get
12+
logger = logging.getLogger(__name__)
13+
14+
def _request(
15+
self,
16+
method,
17+
url,
18+
name='',
19+
parameters=[],
20+
**kwargs
21+
):
22+
"""[summary]
23+
24+
Args:
25+
method ([type]): [description]
26+
url ([type]): [description]
27+
name (str, optional): name of the reqested resource. Defaults to ''.
28+
29+
Raises:
30+
connect_timeout: often indicative of proxy misconfig
31+
read_timeout: often indicative of slow connection
32+
33+
Returns:
34+
json/dict/list: requested object, json decoded.
35+
"""
36+
37+
headers = {
38+
'accept' : 'application/json'
39+
}
40+
headers.update(kwargs.pop('headers', dict()))
41+
42+
if parameters:
43+
url += self._get_parameter_string(parameters)
44+
45+
try:
46+
response = self.session.request(
47+
method=method,
48+
url=url,
49+
headers=headers,
50+
verify=self.verify,
51+
auth=self.auth,
52+
**kwargs
53+
)
54+
55+
if response.status_code / 100 != 2:
56+
self.http_exception_handler(
57+
response=response,
58+
name=name
59+
)
60+
61+
response_json = response.json()
62+
63+
# Do not handle exceptions - just just more details as to possible causes
64+
# Thus we do not catch a JsonDecodeError here even though it may occur
65+
except requests.exceptions.ConnectTimeout as connect_timeout:
66+
logger.critical(f"could not establish a connection within {self.timeout}s, this may be indicative of proxy misconfiguration")
67+
raise connect_timeout
68+
except requests.exceptions.ReadTimeout as read_timeout:
69+
logger.critical(f"slow or unstable connection, consider increasing timeout (currently set to {self.timeout}s)")
70+
raise read_timeout
71+
else:
72+
return response_json
73+
74+
def _get_items(self, url, method='GET', page_size=100, name='', **kwargs):
75+
"""Utility method to get 'pages' of items
76+
77+
Args:
78+
url (str): [description]
79+
method (str, optional): [description]. Defaults to 'GET'.
80+
page_size (int, optional): [description]. Defaults to 100.
81+
name (str, optional): [description]. Defaults to ''.
82+
83+
Yields:
84+
[type]: [description]
85+
"""
86+
offset = 0
87+
params = kwargs.pop('params', dict())
88+
while True:
89+
params.update({'offset':f"{offset}", 'limit':f"{page_size}"})
90+
items = self._request(
91+
method=method,
92+
url=url,
93+
params=params,
94+
name=name,
95+
**kwargs
96+
).get('items', list())
97+
98+
for item in items:
99+
yield item
100+
101+
if len(items) < page_size:
102+
# This will be true if there are no more 'pages' to view
103+
break
104+
105+
offset += page_size
106+
107+
108+
def _get_resource_href(self, resources, resource_name):
109+
"""Utility function to get url for a given resource_name
110+
111+
Args:
112+
resources (dict/json): [description]
113+
resource_name (str): [description]
114+
115+
Raises:
116+
KeyError: on key not found
117+
118+
Returns:
119+
str: url to named resource
120+
"""
121+
res = find_field(
122+
data_to_filter=safe_get(resources, '_meta', 'links'),
123+
field_name='rel',
124+
field_value=resource_name
125+
)
126+
127+
if None == res:
128+
raise KeyError(f"'{self.get_resource_name(resources)}' object has no such key '{resource_name}'")
129+
return safe_get(res, 'href')
130+
131+
def get_resource(self, bd_object, resource_name, iterable=True, is_public=True, **kwargs):
132+
"""Generic function to facilitate subresource fetching
133+
134+
Args:
135+
bd_object (dict/json): [description]
136+
resource_name (str): [description]
137+
iterable (bool, optional): [description]. Defaults to True.
138+
is_public (bool, optional): [description]. Defaults to True.
139+
140+
Returns:
141+
dict/json: named resource object
142+
"""
143+
url = self._get_resource_href(resources=bd_object, resource_name=resource_name) if is_public else self.get_url(bd_object) + f"/{resource_name}"
144+
fn = self._get_items if iterable else self._request
145+
return fn(
146+
method='GET',
147+
url=url,
148+
name=resource_name,
149+
**kwargs
150+
)
151+
152+
def list_resources(self, bd_object):
153+
return [res.get('rel') for res in safe_get(bd_object, '_meta', 'links')]
154+
155+
def _get_base_resource_url(self, resource_name, is_public=True, **kwargs):
156+
if is_public:
157+
resources = self._request(
158+
method="GET",
159+
url=self.base_url + f"/api/",
160+
name='_get_base_resource_url',
161+
**kwargs
162+
)
163+
return resources.get(resource_name, "")
164+
else:
165+
return self.base_url + f"/api/{resource_name}"
166+
167+
def get_base_resource(self, resource_name, is_public=True, **kwargs):
168+
return self._request(
169+
method='GET',
170+
url=self._get_base_resource_url(resource_name, is_public=is_public, **kwargs),
171+
name='get_base_resource',
172+
**kwargs
173+
)
174+
175+
def _get_parameter_string(self, parameters=list()):
176+
return '?' + '&'.join(parameters) if parameters else ''

0 commit comments

Comments
 (0)