Skip to content

Client User Guide

Sumio Kiyooka edited this page May 13, 2021 · 26 revisions

Introduction

The Client class is a binding to Blackduck's REST API that provides a robust connection backed by a Requests session object. The user specifies a base URL, timeout, retries, proxies, and TLS verification upon initialization and these attributes are persisted across all requests.

At the REST API level, the Client class provides a consistent way to discover and traverse public resources, uses a generator to fetch all items using pagination, and automatically renews the bearer token.

Note that unlike HubInstance (from HubRestApi.py), Client does not provide a method for each and every endpoint. Instead it only provides a handful of core methods (Client API Reference) and then encourages the user to leverage the functionality provided by the underlying Requests session and to be comfortable working at that level. See Client versus HubInstance Comparison.

It is designed to provide a solid foundation especially suited for long-running scripts.

Table of Contents

Python 3 is required. Use pip to install the library from PyPI.

pip3 install blackduck

First generate an access token from Blackduck's UI: My Access Tokens

Save the token and provide it to Client. In the following example, the token is saved to an environment variable: blackduck_token

from blackduck import Client
import logging
import os
from pprint import pprint
logging.basicConfig(
level=logging.INFO,
format="[%(asctime)s] {%(module)s:%(lineno)d} %(levelname)s - %(message)s"
)
bd = Client(
token=os.environ.get('blackduck_token'),
base_url="https://your.blackduck.url",
# verify=False # TLS certificate verification
)

Blackduck's REST API provides a way to discover and traverse available resources identified by name. To take advantage of this mechanism, Client provides two key methods: list_resources() and get_resource()

To fetch what public resources are available, use the method list_resources() which returns a Python dict.

pprint(bd.list_resources()) # resources available at the root level

For an object returned from get_resource(), one can invoke list_resources() on it to see what sub-resources are available.

for project in bd.get_resource('projects'):
pprint(bd.list_resources(project))
quit(0)

To see the URL for the resource itself use the 'href' key:

resources_dict = bd.list_resources(project)
print(resources_dict['href'])

To fetch a resource pass in the name (str) provided by an earlier list_resources() call.

If the resource is paginated it will automatically return a generator that will fetch items as required and eventually will return all items. This generator can naturally be used in a for loop:

for project in bd.get_resource('projects'):
print(project['name'])

To fetch a sub-resource, pass in the parent. To get the versions for each project:

for project in bd.get_resource('projects'):
print(project['name'])
for version in bd.get_resource('versions', project):
print(version['versionName'])

If the resource is not paginated pass items=False and a dict will be returned:

bd.get_resource('maxSnippetFileSize', items=False)

This covers advanced topics that are relevant to a user more familiar with the REST API.

Parameters are set using 'params' of session.request. To fetch all users sorted by firstname:

params = {
'sort': 'firstname'
}
bd.get_resource('users', params=params) # constructs url /api/users?sort=firstname

It is possible to provide multiple values for the same key. To fetch all users with the letters 't' AND 'e' in the 'userName':

params = {
'q': ["userName:t", "userName:e"]
}
bd.get_resource('users', params=params) # constructs url: /api/users?q=userName:t&q=userName:e

A more complicated example to get all users with 'e' in the userName AND 'test' in the email, sorting the results by firstname:

params = {
'q': ["userName:e", "email:test"],
'sort': 'firstname',
}
bd.get_resource('users', params=params) # constructs url: /api/users?q=userName:e&q=email:test&sort=firstname

By default, if no 'accept' nor 'content-type' headers are specified with a GET request, the following default media type headers will be inserted:

headers = {
'accept': "application/json",
'content-type': "application/json"
}

Generally "application/json" instructs the REST API to use the most recent external version of an end-point. These headers will work with most (but unfortunately not all) endpoints.

To use a specific media type header specify one of 'accept', 'content-type' (or some combination of both) as required. For example:

headers = {
'accept': "application/vnd.blackducksoftware.user-4+json"
}
items = bd.get_resource('dormantUsers', headers=headers)

In addition to list_resources() and get_resource(), the Client purposely exposes the underlying requests layer so that the user can comfortably work with the request(), get()*, put(), post(), and delete() methods.

* While you can certainly use get() we recommend get_json() instead. See explanation further below.

Accessing the underlying session directly allows a user to customize the URL to send an arbitrary request. It is expected that the user provides any headers and handles the response object. See the very excellent Requests documentation. Requests is the #1 downloaded and used Python library for good reason and we encourage you to spend a few hours to learn more about it (if you haven't done so already) and leverage it when working with the REST API.

response = bd.session.get("/api/projects?offset=0&limit=10")
response.raise_for_status()
json_data = response.json()
items = json_data['items']

As the above construct of http get, check response, and decode the json to a dict is ubiquitous when fetching data, there is a helper method get_json() to streamline to a single call but preserve underlying error handling of HTTPErrors and JSONDecodeErrors.

json_data = bd.get_json("/api/projects?offset=0&limit=10")
items = json_data['items']

To see the request responses from the server, set the logging level to DEBUG

logging.basicConfig(
level=logging.DEBUG,
...
)
# DEBUG - https://your.blackduck.url:443 "GET /api/projects?offset=0&limit=100 HTTP/1.1" 200 None

Use the underlying session to POST data. The dict passed to session.post must use the named parameter 'json'.

For example:

project_data = {
'name': "test project",
'description': "some description",
'projectLevelAdjustments': True,
}
try:
r = bd.session.post("/api/projects", json=project_data)
r.raise_for_status()
print(f"created project {r.links['project']['url']}")
except requests.HTTPError as err:
# more fine grained error handling here; otherwise:
bd.http_error_handler(err)

or something like this:

r = bd.session.post("/api/projects", json=project_data)
if r.status_code == 201:
print(f"created project {r.links['project']['url']}")
else:
bd.http_error_handler(r)

Use the underlying session to PUT data. The dict passed to session.put must use the named parameter 'json'.

For example:

project_url = "/api/projects/c177cb7d-8942-484a-a203-9c2b0582c186" # for a specific project
project_data = {
'name': "test project",
'description': "some other description",
}
try:
r = bd.session.put(project_url, json=project_data)
r.raise_for_status()
print("updated project")
except requests.HTTPError as err:
if err.response.status_code == 404:
print("not found")
else:
bd.http_error_handler(err)

or something like this:

r = bd.session.put(project_url, json=project_data)
if r.status_code == 200:
print("updated project")
elif r.status_code == 404:
print("not found")
else:
bd.http_error_handler(r)

Use the underlying session to DELETE data. For example use this construct:

project_url = "/api/projects/c177cb7d-8942-484a-a203-9c2b0582c186" # for a specific project
try:
r = bd.session.delete(project_url)
r.raise_for_status()
print("deleted project")
except requests.HTTPError as err:
if err.response.status_code == 404:
print("not found")
else:
bd.http_error_handler(err)

or something like this:

r = bd.session.delete(project_url)
if r.status_code == 204:
print("deleted project")
elif r.status_code == 404:
print("not found")
else:
bd.http_error_handler(r)

If your code is already using a class named Client, use Python's import as to avoid a name clash:

from blackduck import Client as HubClient
bd = HubClient(
...
)

Username/password authentication is not recommended and it may be deprecated in the future. It is recommended to use the more secure access token authentication instead.

However, it is still possible to authenticate via username/password if required:

from blackduck import Client
from blackduck.Client import HubSession
from blackduck.Authentication import CookieAuth
import logging
logging.basicConfig(
level=logging.INFO,
format="[%(asctime)s] {%(module)s:%(lineno)d} %(levelname)s - %(message)s"
)
verify = False # TLS certificate verification
base_url = "https://your.blackduck.url"
session = HubSession(base_url, timeout=15.0, retries=3, verify=verify)
auth = CookieAuth(session, "sysadmin", "PASSWORD")
bd = Client(base_url=base_url, session=session, auth=auth)
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Client User Guide · blackducksoftware/hub-rest-api-python Wiki · GitHub
Skip to content

Client User Guide

Sumio Kiyooka edited this page May 13, 2021 · 26 revisions

Introduction

The Client class is a binding to Blackduck's REST API that provides a robust connection backed by a Requests session object. The user specifies a base URL, timeout, retries, proxies, and TLS verification upon initialization and these attributes are persisted across all requests.

At the REST API level, the Client class provides a consistent way to discover and traverse public resources, uses a generator to fetch all items using pagination, and automatically renews the bearer token.

Note that unlike HubInstance (from HubRestApi.py), Client does not provide a method for each and every endpoint. Instead it only provides a handful of core methods (Client API Reference) and then encourages the user to leverage the functionality provided by the underlying Requests session and to be comfortable working at that level. See Client versus HubInstance Comparison.

It is designed to provide a solid foundation especially suited for long-running scripts.

Table of Contents

Python 3 is required. Use pip to install the library from PyPI.

pip3 install blackduck

First generate an access token from Blackduck's UI: My Access Tokens

Save the token and provide it to Client. In the following example, the token is saved to an environment variable: blackduck_token

from blackduck import Client
import logging
import os
from pprint import pprint
logging.basicConfig(
level=logging.INFO,
format="[%(asctime)s] {%(module)s:%(lineno)d} %(levelname)s - %(message)s"
)
bd = Client(
token=os.environ.get('blackduck_token'),
base_url="https://your.blackduck.url",
# verify=False # TLS certificate verification
)

Blackduck's REST API provides a way to discover and traverse available resources identified by name. To take advantage of this mechanism, Client provides two key methods: list_resources() and get_resource()

To fetch what public resources are available, use the method list_resources() which returns a Python dict.

pprint(bd.list_resources()) # resources available at the root level

For an object returned from get_resource(), one can invoke list_resources() on it to see what sub-resources are available.

for project in bd.get_resource('projects'):
pprint(bd.list_resources(project))
quit(0)

To see the URL for the resource itself use the 'href' key:

resources_dict = bd.list_resources(project)
print(resources_dict['href'])

To fetch a resource pass in the name (str) provided by an earlier list_resources() call.

If the resource is paginated it will automatically return a generator that will fetch items as required and eventually will return all items. This generator can naturally be used in a for loop:

for project in bd.get_resource('projects'):
print(project['name'])

To fetch a sub-resource, pass in the parent. To get the versions for each project:

for project in bd.get_resource('projects'):
print(project['name'])
for version in bd.get_resource('versions', project):
print(version['versionName'])

If the resource is not paginated pass items=False and a dict will be returned:

bd.get_resource('maxSnippetFileSize', items=False)

This covers advanced topics that are relevant to a user more familiar with the REST API.

Parameters are set using 'params' of session.request. To fetch all users sorted by firstname:

params = {
'sort': 'firstname'
}
bd.get_resource('users', params=params) # constructs url /api/users?sort=firstname

It is possible to provide multiple values for the same key. To fetch all users with the letters 't' AND 'e' in the 'userName':

params = {
'q': ["userName:t", "userName:e"]
}
bd.get_resource('users', params=params) # constructs url: /api/users?q=userName:t&q=userName:e

A more complicated example to get all users with 'e' in the userName AND 'test' in the email, sorting the results by firstname:

params = {
'q': ["userName:e", "email:test"],
'sort': 'firstname',
}
bd.get_resource('users', params=params) # constructs url: /api/users?q=userName:e&q=email:test&sort=firstname

By default, if no 'accept' nor 'content-type' headers are specified with a GET request, the following default media type headers will be inserted:

headers = {
'accept': "application/json",
'content-type': "application/json"
}

Generally "application/json" instructs the REST API to use the most recent external version of an end-point. These headers will work with most (but unfortunately not all) endpoints.

To use a specific media type header specify one of 'accept', 'content-type' (or some combination of both) as required. For example:

headers = {
'accept': "application/vnd.blackducksoftware.user-4+json"
}
items = bd.get_resource('dormantUsers', headers=headers)

In addition to list_resources() and get_resource(), the Client purposely exposes the underlying requests layer so that the user can comfortably work with the request(), get()*, put(), post(), and delete() methods.

* While you can certainly use get() we recommend get_json() instead. See explanation further below.

Accessing the underlying session directly allows a user to customize the URL to send an arbitrary request. It is expected that the user provides any headers and handles the response object. See the very excellent Requests documentation. Requests is the #1 downloaded and used Python library for good reason and we encourage you to spend a few hours to learn more about it (if you haven't done so already) and leverage it when working with the REST API.

response = bd.session.get("/api/projects?offset=0&limit=10")
response.raise_for_status()
json_data = response.json()
items = json_data['items']

As the above construct of http get, check response, and decode the json to a dict is ubiquitous when fetching data, there is a helper method get_json() to streamline to a single call but preserve underlying error handling of HTTPErrors and JSONDecodeErrors.

json_data = bd.get_json("/api/projects?offset=0&limit=10")
items = json_data['items']

To see the request responses from the server, set the logging level to DEBUG

logging.basicConfig(
level=logging.DEBUG,
...
)
# DEBUG - https://your.blackduck.url:443 "GET /api/projects?offset=0&limit=100 HTTP/1.1" 200 None

Use the underlying session to POST data. The dict passed to session.post must use the named parameter 'json'.

For example:

project_data = {
'name': "test project",
'description': "some description",
'projectLevelAdjustments': True,
}
try:
r = bd.session.post("/api/projects", json=project_data)
r.raise_for_status()
print(f"created project {r.links['project']['url']}")
except requests.HTTPError as err:
# more fine grained error handling here; otherwise:
bd.http_error_handler(err)

or something like this:

r = bd.session.post("/api/projects", json=project_data)
if r.status_code == 201:
print(f"created project {r.links['project']['url']}")
else:
bd.http_error_handler(r)

Use the underlying session to PUT data. The dict passed to session.put must use the named parameter 'json'.

For example:

project_url = "/api/projects/c177cb7d-8942-484a-a203-9c2b0582c186" # for a specific project
project_data = {
'name': "test project",
'description': "some other description",
}
try:
r = bd.session.put(project_url, json=project_data)
r.raise_for_status()
print("updated project")
except requests.HTTPError as err:
if err.response.status_code == 404:
print("not found")
else:
bd.http_error_handler(err)

or something like this:

r = bd.session.put(project_url, json=project_data)
if r.status_code == 200:
print("updated project")
elif r.status_code == 404:
print("not found")
else:
bd.http_error_handler(r)

Use the underlying session to DELETE data. For example use this construct:

project_url = "/api/projects/c177cb7d-8942-484a-a203-9c2b0582c186" # for a specific project
try:
r = bd.session.delete(project_url)
r.raise_for_status()
print("deleted project")
except requests.HTTPError as err:
if err.response.status_code == 404:
print("not found")
else:
bd.http_error_handler(err)

or something like this:

r = bd.session.delete(project_url)
if r.status_code == 204:
print("deleted project")
elif r.status_code == 404:
print("not found")
else:
bd.http_error_handler(r)

If your code is already using a class named Client, use Python's import as to avoid a name clash:

from blackduck import Client as HubClient
bd = HubClient(
...
)

Username/password authentication is not recommended and it may be deprecated in the future. It is recommended to use the more secure access token authentication instead.

However, it is still possible to authenticate via username/password if required:

from blackduck import Client
from blackduck.Client import HubSession
from blackduck.Authentication import CookieAuth
import logging
logging.basicConfig(
level=logging.INFO,
format="[%(asctime)s] {%(module)s:%(lineno)d} %(levelname)s - %(message)s"
)
verify = False # TLS certificate verification
base_url = "https://your.blackduck.url"
session = HubSession(base_url, timeout=15.0, retries=3, verify=verify)
auth = CookieAuth(session, "sysadmin", "PASSWORD")
bd = Client(base_url=base_url, session=session, auth=auth)
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Client User Guide · blackducksoftware/hub-rest-api-python Wiki · GitHub
Skip to content

Client User Guide

Sumio Kiyooka edited this page May 13, 2021 · 26 revisions

Introduction

The Client class is a binding to Blackduck's REST API that provides a robust connection backed by a Requests session object. The user specifies a base URL, timeout, retries, proxies, and TLS verification upon initialization and these attributes are persisted across all requests.

At the REST API level, the Client class provides a consistent way to discover and traverse public resources, uses a generator to fetch all items using pagination, and automatically renews the bearer token.

Note that unlike HubInstance (from HubRestApi.py), Client does not provide a method for each and every endpoint. Instead it only provides a handful of core methods (Client API Reference) and then encourages the user to leverage the functionality provided by the underlying Requests session and to be comfortable working at that level. See Client versus HubInstance Comparison.

It is designed to provide a solid foundation especially suited for long-running scripts.

Table of Contents

Python 3 is required. Use pip to install the library from PyPI.

pip3 install blackduck

First generate an access token from Blackduck's UI: My Access Tokens

Save the token and provide it to Client. In the following example, the token is saved to an environment variable: blackduck_token

from blackduck import Client
import logging
import os
from pprint import pprint
logging.basicConfig(
level=logging.INFO,
format="[%(asctime)s] {%(module)s:%(lineno)d} %(levelname)s - %(message)s"
)
bd = Client(
token=os.environ.get('blackduck_token'),
base_url="https://your.blackduck.url",
# verify=False # TLS certificate verification
)

Blackduck's REST API provides a way to discover and traverse available resources identified by name. To take advantage of this mechanism, Client provides two key methods: list_resources() and get_resource()

To fetch what public resources are available, use the method list_resources() which returns a Python dict.

pprint(bd.list_resources()) # resources available at the root level

For an object returned from get_resource(), one can invoke list_resources() on it to see what sub-resources are available.

for project in bd.get_resource('projects'):
pprint(bd.list_resources(project))
quit(0)

To see the URL for the resource itself use the 'href' key:

resources_dict = bd.list_resources(project)
print(resources_dict['href'])

To fetch a resource pass in the name (str) provided by an earlier list_resources() call.

If the resource is paginated it will automatically return a generator that will fetch items as required and eventually will return all items. This generator can naturally be used in a for loop:

for project in bd.get_resource('projects'):
print(project['name'])

To fetch a sub-resource, pass in the parent. To get the versions for each project:

for project in bd.get_resource('projects'):
print(project['name'])
for version in bd.get_resource('versions', project):
print(version['versionName'])

If the resource is not paginated pass items=False and a dict will be returned:

bd.get_resource('maxSnippetFileSize', items=False)

This covers advanced topics that are relevant to a user more familiar with the REST API.

Parameters are set using 'params' of session.request. To fetch all users sorted by firstname:

params = {
'sort': 'firstname'
}
bd.get_resource('users', params=params) # constructs url /api/users?sort=firstname

It is possible to provide multiple values for the same key. To fetch all users with the letters 't' AND 'e' in the 'userName':

params = {
'q': ["userName:t", "userName:e"]
}
bd.get_resource('users', params=params) # constructs url: /api/users?q=userName:t&q=userName:e

A more complicated example to get all users with 'e' in the userName AND 'test' in the email, sorting the results by firstname:

params = {
'q': ["userName:e", "email:test"],
'sort': 'firstname',
}
bd.get_resource('users', params=params) # constructs url: /api/users?q=userName:e&q=email:test&sort=firstname

By default, if no 'accept' nor 'content-type' headers are specified with a GET request, the following default media type headers will be inserted:

headers = {
'accept': "application/json",
'content-type': "application/json"
}

Generally "application/json" instructs the REST API to use the most recent external version of an end-point. These headers will work with most (but unfortunately not all) endpoints.

To use a specific media type header specify one of 'accept', 'content-type' (or some combination of both) as required. For example:

headers = {
'accept': "application/vnd.blackducksoftware.user-4+json"
}
items = bd.get_resource('dormantUsers', headers=headers)

In addition to list_resources() and get_resource(), the Client purposely exposes the underlying requests layer so that the user can comfortably work with the request(), get()*, put(), post(), and delete() methods.

* While you can certainly use get() we recommend get_json() instead. See explanation further below.

Accessing the underlying session directly allows a user to customize the URL to send an arbitrary request. It is expected that the user provides any headers and handles the response object. See the very excellent Requests documentation. Requests is the #1 downloaded and used Python library for good reason and we encourage you to spend a few hours to learn more about it (if you haven't done so already) and leverage it when working with the REST API.

response = bd.session.get("/api/projects?offset=0&limit=10")
response.raise_for_status()
json_data = response.json()
items = json_data['items']

As the above construct of http get, check response, and decode the json to a dict is ubiquitous when fetching data, there is a helper method get_json() to streamline to a single call but preserve underlying error handling of HTTPErrors and JSONDecodeErrors.

json_data = bd.get_json("/api/projects?offset=0&limit=10")
items = json_data['items']

To see the request responses from the server, set the logging level to DEBUG

logging.basicConfig(
level=logging.DEBUG,
...
)
# DEBUG - https://your.blackduck.url:443 "GET /api/projects?offset=0&limit=100 HTTP/1.1" 200 None

Use the underlying session to POST data. The dict passed to session.post must use the named parameter 'json'.

For example:

project_data = {
'name': "test project",
'description': "some description",
'projectLevelAdjustments': True,
}
try:
r = bd.session.post("/api/projects", json=project_data)
r.raise_for_status()
print(f"created project {r.links['project']['url']}")
except requests.HTTPError as err:
# more fine grained error handling here; otherwise:
bd.http_error_handler(err)

or something like this:

r = bd.session.post("/api/projects", json=project_data)
if r.status_code == 201:
print(f"created project {r.links['project']['url']}")
else:
bd.http_error_handler(r)

Use the underlying session to PUT data. The dict passed to session.put must use the named parameter 'json'.

For example:

project_url = "/api/projects/c177cb7d-8942-484a-a203-9c2b0582c186" # for a specific project
project_data = {
'name': "test project",
'description': "some other description",
}
try:
r = bd.session.put(project_url, json=project_data)
r.raise_for_status()
print("updated project")
except requests.HTTPError as err:
if err.response.status_code == 404:
print("not found")
else:
bd.http_error_handler(err)

or something like this:

r = bd.session.put(project_url, json=project_data)
if r.status_code == 200:
print("updated project")
elif r.status_code == 404:
print("not found")
else:
bd.http_error_handler(r)

Use the underlying session to DELETE data. For example use this construct:

project_url = "/api/projects/c177cb7d-8942-484a-a203-9c2b0582c186" # for a specific project
try:
r = bd.session.delete(project_url)
r.raise_for_status()
print("deleted project")
except requests.HTTPError as err:
if err.response.status_code == 404:
print("not found")
else:
bd.http_error_handler(err)

or something like this:

r = bd.session.delete(project_url)
if r.status_code == 204:
print("deleted project")
elif r.status_code == 404:
print("not found")
else:
bd.http_error_handler(r)

If your code is already using a class named Client, use Python's import as to avoid a name clash:

from blackduck import Client as HubClient
bd = HubClient(
...
)

Username/password authentication is not recommended and it may be deprecated in the future. It is recommended to use the more secure access token authentication instead.

However, it is still possible to authenticate via username/password if required:

from blackduck import Client
from blackduck.Client import HubSession
from blackduck.Authentication import CookieAuth
import logging
logging.basicConfig(
level=logging.INFO,
format="[%(asctime)s] {%(module)s:%(lineno)d} %(levelname)s - %(message)s"
)
verify = False # TLS certificate verification
base_url = "https://your.blackduck.url"
session = HubSession(base_url, timeout=15.0, retries=3, verify=verify)
auth = CookieAuth(session, "sysadmin", "PASSWORD")
bd = Client(base_url=base_url, session=session, auth=auth)
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Client User Guide · blackducksoftware/hub-rest-api-python Wiki · GitHub
Skip to content

Client User Guide

Sumio Kiyooka edited this page May 13, 2021 · 26 revisions

Introduction

The Client class is a binding to Blackduck's REST API that provides a robust connection backed by a Requests session object. The user specifies a base URL, timeout, retries, proxies, and TLS verification upon initialization and these attributes are persisted across all requests.

At the REST API level, the Client class provides a consistent way to discover and traverse public resources, uses a generator to fetch all items using pagination, and automatically renews the bearer token.

Note that unlike HubInstance (from HubRestApi.py), Client does not provide a method for each and every endpoint. Instead it only provides a handful of core methods (Client API Reference) and then encourages the user to leverage the functionality provided by the underlying Requests session and to be comfortable working at that level. See Client versus HubInstance Comparison.

It is designed to provide a solid foundation especially suited for long-running scripts.

Table of Contents

Python 3 is required. Use pip to install the library from PyPI.

pip3 install blackduck

First generate an access token from Blackduck's UI: My Access Tokens

Save the token and provide it to Client. In the following example, the token is saved to an environment variable: blackduck_token

from blackduck import Client
import logging
import os
from pprint import pprint
logging.basicConfig(
level=logging.INFO,
format="[%(asctime)s] {%(module)s:%(lineno)d} %(levelname)s - %(message)s"
)
bd = Client(
token=os.environ.get('blackduck_token'),
base_url="https://your.blackduck.url",
# verify=False # TLS certificate verification
)

Blackduck's REST API provides a way to discover and traverse available resources identified by name. To take advantage of this mechanism, Client provides two key methods: list_resources() and get_resource()

To fetch what public resources are available, use the method list_resources() which returns a Python dict.

pprint(bd.list_resources()) # resources available at the root level

For an object returned from get_resource(), one can invoke list_resources() on it to see what sub-resources are available.

for project in bd.get_resource('projects'):
pprint(bd.list_resources(project))
quit(0)

To see the URL for the resource itself use the 'href' key:

resources_dict = bd.list_resources(project)
print(resources_dict['href'])

To fetch a resource pass in the name (str) provided by an earlier list_resources() call.

If the resource is paginated it will automatically return a generator that will fetch items as required and eventually will return all items. This generator can naturally be used in a for loop:

for project in bd.get_resource('projects'):
print(project['name'])

To fetch a sub-resource, pass in the parent. To get the versions for each project:

for project in bd.get_resource('projects'):
print(project['name'])
for version in bd.get_resource('versions', project):
print(version['versionName'])

If the resource is not paginated pass items=False and a dict will be returned:

bd.get_resource('maxSnippetFileSize', items=False)

This covers advanced topics that are relevant to a user more familiar with the REST API.

Parameters are set using 'params' of session.request. To fetch all users sorted by firstname:

params = {
'sort': 'firstname'
}
bd.get_resource('users', params=params) # constructs url /api/users?sort=firstname

It is possible to provide multiple values for the same key. To fetch all users with the letters 't' AND 'e' in the 'userName':

params = {
'q': ["userName:t", "userName:e"]
}
bd.get_resource('users', params=params) # constructs url: /api/users?q=userName:t&q=userName:e

A more complicated example to get all users with 'e' in the userName AND 'test' in the email, sorting the results by firstname:

params = {
'q': ["userName:e", "email:test"],
'sort': 'firstname',
}
bd.get_resource('users', params=params) # constructs url: /api/users?q=userName:e&q=email:test&sort=firstname

By default, if no 'accept' nor 'content-type' headers are specified with a GET request, the following default media type headers will be inserted:

headers = {
'accept': "application/json",
'content-type': "application/json"
}

Generally "application/json" instructs the REST API to use the most recent external version of an end-point. These headers will work with most (but unfortunately not all) endpoints.

To use a specific media type header specify one of 'accept', 'content-type' (or some combination of both) as required. For example:

headers = {
'accept': "application/vnd.blackducksoftware.user-4+json"
}
items = bd.get_resource('dormantUsers', headers=headers)

In addition to list_resources() and get_resource(), the Client purposely exposes the underlying requests layer so that the user can comfortably work with the request(), get()*, put(), post(), and delete() methods.

* While you can certainly use get() we recommend get_json() instead. See explanation further below.

Accessing the underlying session directly allows a user to customize the URL to send an arbitrary request. It is expected that the user provides any headers and handles the response object. See the very excellent Requests documentation. Requests is the #1 downloaded and used Python library for good reason and we encourage you to spend a few hours to learn more about it (if you haven't done so already) and leverage it when working with the REST API.

response = bd.session.get("/api/projects?offset=0&limit=10")
response.raise_for_status()
json_data = response.json()
items = json_data['items']

As the above construct of http get, check response, and decode the json to a dict is ubiquitous when fetching data, there is a helper method get_json() to streamline to a single call but preserve underlying error handling of HTTPErrors and JSONDecodeErrors.

json_data = bd.get_json("/api/projects?offset=0&limit=10")
items = json_data['items']

To see the request responses from the server, set the logging level to DEBUG

logging.basicConfig(
level=logging.DEBUG,
...
)
# DEBUG - https://your.blackduck.url:443 "GET /api/projects?offset=0&limit=100 HTTP/1.1" 200 None

Use the underlying session to POST data. The dict passed to session.post must use the named parameter 'json'.

For example:

project_data = {
'name': "test project",
'description': "some description",
'projectLevelAdjustments': True,
}
try:
r = bd.session.post("/api/projects", json=project_data)
r.raise_for_status()
print(f"created project {r.links['project']['url']}")
except requests.HTTPError as err:
# more fine grained error handling here; otherwise:
bd.http_error_handler(err)

or something like this:

r = bd.session.post("/api/projects", json=project_data)
if r.status_code == 201:
print(f"created project {r.links['project']['url']}")
else:
bd.http_error_handler(r)

Use the underlying session to PUT data. The dict passed to session.put must use the named parameter 'json'.

For example:

project_url = "/api/projects/c177cb7d-8942-484a-a203-9c2b0582c186" # for a specific project
project_data = {
'name': "test project",
'description': "some other description",
}
try:
r = bd.session.put(project_url, json=project_data)
r.raise_for_status()
print("updated project")
except requests.HTTPError as err:
if err.response.status_code == 404:
print("not found")
else:
bd.http_error_handler(err)

or something like this:

r = bd.session.put(project_url, json=project_data)
if r.status_code == 200:
print("updated project")
elif r.status_code == 404:
print("not found")
else:
bd.http_error_handler(r)

Use the underlying session to DELETE data. For example use this construct:

project_url = "/api/projects/c177cb7d-8942-484a-a203-9c2b0582c186" # for a specific project
try:
r = bd.session.delete(project_url)
r.raise_for_status()
print("deleted project")
except requests.HTTPError as err:
if err.response.status_code == 404:
print("not found")
else:
bd.http_error_handler(err)

or something like this:

r = bd.session.delete(project_url)
if r.status_code == 204:
print("deleted project")
elif r.status_code == 404:
print("not found")
else:
bd.http_error_handler(r)

If your code is already using a class named Client, use Python's import as to avoid a name clash:

from blackduck import Client as HubClient
bd = HubClient(
...
)

Username/password authentication is not recommended and it may be deprecated in the future. It is recommended to use the more secure access token authentication instead.

However, it is still possible to authenticate via username/password if required:

from blackduck import Client
from blackduck.Client import HubSession
from blackduck.Authentication import CookieAuth
import logging
logging.basicConfig(
level=logging.INFO,
format="[%(asctime)s] {%(module)s:%(lineno)d} %(levelname)s - %(message)s"
)
verify = False # TLS certificate verification
base_url = "https://your.blackduck.url"
session = HubSession(base_url, timeout=15.0, retries=3, verify=verify)
auth = CookieAuth(session, "sysadmin", "PASSWORD")
bd = Client(base_url=base_url, session=session, auth=auth)
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Client User Guide · blackducksoftware/hub-rest-api-python Wiki · GitHub
Skip to content

Client User Guide

Sumio Kiyooka edited this page May 13, 2021 · 26 revisions

Introduction

The Client class is a binding to Blackduck's REST API that provides a robust connection backed by a Requests session object. The user specifies a base URL, timeout, retries, proxies, and TLS verification upon initialization and these attributes are persisted across all requests.

At the REST API level, the Client class provides a consistent way to discover and traverse public resources, uses a generator to fetch all items using pagination, and automatically renews the bearer token.

Note that unlike HubInstance (from HubRestApi.py), Client does not provide a method for each and every endpoint. Instead it only provides a handful of core methods (Client API Reference) and then encourages the user to leverage the functionality provided by the underlying Requests session and to be comfortable working at that level. See Client versus HubInstance Comparison.

It is designed to provide a solid foundation especially suited for long-running scripts.

Table of Contents

Python 3 is required. Use pip to install the library from PyPI.

pip3 install blackduck

First generate an access token from Blackduck's UI: My Access Tokens

Save the token and provide it to Client. In the following example, the token is saved to an environment variable: blackduck_token

from blackduck import Client
import logging
import os
from pprint import pprint
logging.basicConfig(
level=logging.INFO,
format="[%(asctime)s] {%(module)s:%(lineno)d} %(levelname)s - %(message)s"
)
bd = Client(
token=os.environ.get('blackduck_token'),
base_url="https://your.blackduck.url",
# verify=False # TLS certificate verification
)

Blackduck's REST API provides a way to discover and traverse available resources identified by name. To take advantage of this mechanism, Client provides two key methods: list_resources() and get_resource()

To fetch what public resources are available, use the method list_resources() which returns a Python dict.

pprint(bd.list_resources()) # resources available at the root level

For an object returned from get_resource(), one can invoke list_resources() on it to see what sub-resources are available.

for project in bd.get_resource('projects'):
pprint(bd.list_resources(project))
quit(0)

To see the URL for the resource itself use the 'href' key:

resources_dict = bd.list_resources(project)
print(resources_dict['href'])

To fetch a resource pass in the name (str) provided by an earlier list_resources() call.

If the resource is paginated it will automatically return a generator that will fetch items as required and eventually will return all items. This generator can naturally be used in a for loop:

for project in bd.get_resource('projects'):
print(project['name'])

To fetch a sub-resource, pass in the parent. To get the versions for each project:

for project in bd.get_resource('projects'):
print(project['name'])
for version in bd.get_resource('versions', project):
print(version['versionName'])

If the resource is not paginated pass items=False and a dict will be returned:

bd.get_resource('maxSnippetFileSize', items=False)

This covers advanced topics that are relevant to a user more familiar with the REST API.

Parameters are set using 'params' of session.request. To fetch all users sorted by firstname:

params = {
'sort': 'firstname'
}
bd.get_resource('users', params=params) # constructs url /api/users?sort=firstname

It is possible to provide multiple values for the same key. To fetch all users with the letters 't' AND 'e' in the 'userName':

params = {
'q': ["userName:t", "userName:e"]
}
bd.get_resource('users', params=params) # constructs url: /api/users?q=userName:t&q=userName:e

A more complicated example to get all users with 'e' in the userName AND 'test' in the email, sorting the results by firstname:

params = {
'q': ["userName:e", "email:test"],
'sort': 'firstname',
}
bd.get_resource('users', params=params) # constructs url: /api/users?q=userName:e&q=email:test&sort=firstname

By default, if no 'accept' nor 'content-type' headers are specified with a GET request, the following default media type headers will be inserted:

headers = {
'accept': "application/json",
'content-type': "application/json"
}

Generally "application/json" instructs the REST API to use the most recent external version of an end-point. These headers will work with most (but unfortunately not all) endpoints.

To use a specific media type header specify one of 'accept', 'content-type' (or some combination of both) as required. For example:

headers = {
'accept': "application/vnd.blackducksoftware.user-4+json"
}
items = bd.get_resource('dormantUsers', headers=headers)

In addition to list_resources() and get_resource(), the Client purposely exposes the underlying requests layer so that the user can comfortably work with the request(), get()*, put(), post(), and delete() methods.

* While you can certainly use get() we recommend get_json() instead. See explanation further below.

Accessing the underlying session directly allows a user to customize the URL to send an arbitrary request. It is expected that the user provides any headers and handles the response object. See the very excellent Requests documentation. Requests is the #1 downloaded and used Python library for good reason and we encourage you to spend a few hours to learn more about it (if you haven't done so already) and leverage it when working with the REST API.

response = bd.session.get("/api/projects?offset=0&limit=10")
response.raise_for_status()
json_data = response.json()
items = json_data['items']

As the above construct of http get, check response, and decode the json to a dict is ubiquitous when fetching data, there is a helper method get_json() to streamline to a single call but preserve underlying error handling of HTTPErrors and JSONDecodeErrors.

json_data = bd.get_json("/api/projects?offset=0&limit=10")
items = json_data['items']

To see the request responses from the server, set the logging level to DEBUG

logging.basicConfig(
level=logging.DEBUG,
...
)
# DEBUG - https://your.blackduck.url:443 "GET /api/projects?offset=0&limit=100 HTTP/1.1" 200 None

Use the underlying session to POST data. The dict passed to session.post must use the named parameter 'json'.

For example:

project_data = {
'name': "test project",
'description': "some description",
'projectLevelAdjustments': True,
}
try:
r = bd.session.post("/api/projects", json=project_data)
r.raise_for_status()
print(f"created project {r.links['project']['url']}")
except requests.HTTPError as err:
# more fine grained error handling here; otherwise:
bd.http_error_handler(err)

or something like this:

r = bd.session.post("/api/projects", json=project_data)
if r.status_code == 201:
print(f"created project {r.links['project']['url']}")
else:
bd.http_error_handler(r)

Use the underlying session to PUT data. The dict passed to session.put must use the named parameter 'json'.

For example:

project_url = "/api/projects/c177cb7d-8942-484a-a203-9c2b0582c186" # for a specific project
project_data = {
'name': "test project",
'description': "some other description",
}
try:
r = bd.session.put(project_url, json=project_data)
r.raise_for_status()
print("updated project")
except requests.HTTPError as err:
if err.response.status_code == 404:
print("not found")
else:
bd.http_error_handler(err)

or something like this:

r = bd.session.put(project_url, json=project_data)
if r.status_code == 200:
print("updated project")
elif r.status_code == 404:
print("not found")
else:
bd.http_error_handler(r)

Use the underlying session to DELETE data. For example use this construct:

project_url = "/api/projects/c177cb7d-8942-484a-a203-9c2b0582c186" # for a specific project
try:
r = bd.session.delete(project_url)
r.raise_for_status()
print("deleted project")
except requests.HTTPError as err:
if err.response.status_code == 404:
print("not found")
else:
bd.http_error_handler(err)

or something like this:

r = bd.session.delete(project_url)
if r.status_code == 204:
print("deleted project")
elif r.status_code == 404:
print("not found")
else:
bd.http_error_handler(r)

If your code is already using a class named Client, use Python's import as to avoid a name clash:

from blackduck import Client as HubClient
bd = HubClient(
...
)

Username/password authentication is not recommended and it may be deprecated in the future. It is recommended to use the more secure access token authentication instead.

However, it is still possible to authenticate via username/password if required:

from blackduck import Client
from blackduck.Client import HubSession
from blackduck.Authentication import CookieAuth
import logging
logging.basicConfig(
level=logging.INFO,
format="[%(asctime)s] {%(module)s:%(lineno)d} %(levelname)s - %(message)s"
)
verify = False # TLS certificate verification
base_url = "https://your.blackduck.url"
session = HubSession(base_url, timeout=15.0, retries=3, verify=verify)
auth = CookieAuth(session, "sysadmin", "PASSWORD")
bd = Client(base_url=base_url, session=session, auth=auth)
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Client User Guide · blackducksoftware/hub-rest-api-python Wiki · GitHub
Skip to content

Client User Guide

Sumio Kiyooka edited this page May 13, 2021 · 26 revisions

Introduction

The Client class is a binding to Blackduck's REST API that provides a robust connection backed by a Requests session object. The user specifies a base URL, timeout, retries, proxies, and TLS verification upon initialization and these attributes are persisted across all requests.

At the REST API level, the Client class provides a consistent way to discover and traverse public resources, uses a generator to fetch all items using pagination, and automatically renews the bearer token.

Note that unlike HubInstance (from HubRestApi.py), Client does not provide a method for each and every endpoint. Instead it only provides a handful of core methods (Client API Reference) and then encourages the user to leverage the functionality provided by the underlying Requests session and to be comfortable working at that level. See Client versus HubInstance Comparison.

It is designed to provide a solid foundation especially suited for long-running scripts.

Table of Contents

Python 3 is required. Use pip to install the library from PyPI.

pip3 install blackduck

First generate an access token from Blackduck's UI: My Access Tokens

Save the token and provide it to Client. In the following example, the token is saved to an environment variable: blackduck_token

from blackduck import Client
import logging
import os
from pprint import pprint
logging.basicConfig(
level=logging.INFO,
format="[%(asctime)s] {%(module)s:%(lineno)d} %(levelname)s - %(message)s"
)
bd = Client(
token=os.environ.get('blackduck_token'),
base_url="https://your.blackduck.url",
# verify=False # TLS certificate verification
)

Blackduck's REST API provides a way to discover and traverse available resources identified by name. To take advantage of this mechanism, Client provides two key methods: list_resources() and get_resource()

To fetch what public resources are available, use the method list_resources() which returns a Python dict.

pprint(bd.list_resources()) # resources available at the root level

For an object returned from get_resource(), one can invoke list_resources() on it to see what sub-resources are available.

for project in bd.get_resource('projects'):
pprint(bd.list_resources(project))
quit(0)

To see the URL for the resource itself use the 'href' key:

resources_dict = bd.list_resources(project)
print(resources_dict['href'])

To fetch a resource pass in the name (str) provided by an earlier list_resources() call.

If the resource is paginated it will automatically return a generator that will fetch items as required and eventually will return all items. This generator can naturally be used in a for loop:

for project in bd.get_resource('projects'):
print(project['name'])

To fetch a sub-resource, pass in the parent. To get the versions for each project:

for project in bd.get_resource('projects'):
print(project['name'])
for version in bd.get_resource('versions', project):
print(version['versionName'])

If the resource is not paginated pass items=False and a dict will be returned:

bd.get_resource('maxSnippetFileSize', items=False)

This covers advanced topics that are relevant to a user more familiar with the REST API.

Parameters are set using 'params' of session.request. To fetch all users sorted by firstname:

params = {
'sort': 'firstname'
}
bd.get_resource('users', params=params) # constructs url /api/users?sort=firstname

It is possible to provide multiple values for the same key. To fetch all users with the letters 't' AND 'e' in the 'userName':

params = {
'q': ["userName:t", "userName:e"]
}
bd.get_resource('users', params=params) # constructs url: /api/users?q=userName:t&q=userName:e

A more complicated example to get all users with 'e' in the userName AND 'test' in the email, sorting the results by firstname:

params = {
'q': ["userName:e", "email:test"],
'sort': 'firstname',
}
bd.get_resource('users', params=params) # constructs url: /api/users?q=userName:e&q=email:test&sort=firstname

By default, if no 'accept' nor 'content-type' headers are specified with a GET request, the following default media type headers will be inserted:

headers = {
'accept': "application/json",
'content-type': "application/json"
}

Generally "application/json" instructs the REST API to use the most recent external version of an end-point. These headers will work with most (but unfortunately not all) endpoints.

To use a specific media type header specify one of 'accept', 'content-type' (or some combination of both) as required. For example:

headers = {
'accept': "application/vnd.blackducksoftware.user-4+json"
}
items = bd.get_resource('dormantUsers', headers=headers)

In addition to list_resources() and get_resource(), the Client purposely exposes the underlying requests layer so that the user can comfortably work with the request(), get()*, put(), post(), and delete() methods.

* While you can certainly use get() we recommend get_json() instead. See explanation further below.

Accessing the underlying session directly allows a user to customize the URL to send an arbitrary request. It is expected that the user provides any headers and handles the response object. See the very excellent Requests documentation. Requests is the #1 downloaded and used Python library for good reason and we encourage you to spend a few hours to learn more about it (if you haven't done so already) and leverage it when working with the REST API.

response = bd.session.get("/api/projects?offset=0&limit=10")
response.raise_for_status()
json_data = response.json()
items = json_data['items']

As the above construct of http get, check response, and decode the json to a dict is ubiquitous when fetching data, there is a helper method get_json() to streamline to a single call but preserve underlying error handling of HTTPErrors and JSONDecodeErrors.

json_data = bd.get_json("/api/projects?offset=0&limit=10")
items = json_data['items']

To see the request responses from the server, set the logging level to DEBUG

logging.basicConfig(
level=logging.DEBUG,
...
)
# DEBUG - https://your.blackduck.url:443 "GET /api/projects?offset=0&limit=100 HTTP/1.1" 200 None

Use the underlying session to POST data. The dict passed to session.post must use the named parameter 'json'.

For example:

project_data = {
'name': "test project",
'description': "some description",
'projectLevelAdjustments': True,
}
try:
r = bd.session.post("/api/projects", json=project_data)
r.raise_for_status()
print(f"created project {r.links['project']['url']}")
except requests.HTTPError as err:
# more fine grained error handling here; otherwise:
bd.http_error_handler(err)

or something like this:

r = bd.session.post("/api/projects", json=project_data)
if r.status_code == 201:
print(f"created project {r.links['project']['url']}")
else:
bd.http_error_handler(r)

Use the underlying session to PUT data. The dict passed to session.put must use the named parameter 'json'.

For example:

project_url = "/api/projects/c177cb7d-8942-484a-a203-9c2b0582c186" # for a specific project
project_data = {
'name': "test project",
'description': "some other description",
}
try:
r = bd.session.put(project_url, json=project_data)
r.raise_for_status()
print("updated project")
except requests.HTTPError as err:
if err.response.status_code == 404:
print("not found")
else:
bd.http_error_handler(err)

or something like this:

r = bd.session.put(project_url, json=project_data)
if r.status_code == 200:
print("updated project")
elif r.status_code == 404:
print("not found")
else:
bd.http_error_handler(r)

Use the underlying session to DELETE data. For example use this construct:

project_url = "/api/projects/c177cb7d-8942-484a-a203-9c2b0582c186" # for a specific project
try:
r = bd.session.delete(project_url)
r.raise_for_status()
print("deleted project")
except requests.HTTPError as err:
if err.response.status_code == 404:
print("not found")
else:
bd.http_error_handler(err)

or something like this:

r = bd.session.delete(project_url)
if r.status_code == 204:
print("deleted project")
elif r.status_code == 404:
print("not found")
else:
bd.http_error_handler(r)

If your code is already using a class named Client, use Python's import as to avoid a name clash:

from blackduck import Client as HubClient
bd = HubClient(
...
)

Username/password authentication is not recommended and it may be deprecated in the future. It is recommended to use the more secure access token authentication instead.

However, it is still possible to authenticate via username/password if required:

from blackduck import Client
from blackduck.Client import HubSession
from blackduck.Authentication import CookieAuth
import logging
logging.basicConfig(
level=logging.INFO,
format="[%(asctime)s] {%(module)s:%(lineno)d} %(levelname)s - %(message)s"
)
verify = False # TLS certificate verification
base_url = "https://your.blackduck.url"
session = HubSession(base_url, timeout=15.0, retries=3, verify=verify)
auth = CookieAuth(session, "sysadmin", "PASSWORD")
bd = Client(base_url=base_url, session=session, auth=auth)
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Client User Guide · blackducksoftware/hub-rest-api-python Wiki · GitHub
Skip to content

Client User Guide

Sumio Kiyooka edited this page May 13, 2021 · 26 revisions

Introduction

The Client class is a binding to Blackduck's REST API that provides a robust connection backed by a Requests session object. The user specifies a base URL, timeout, retries, proxies, and TLS verification upon initialization and these attributes are persisted across all requests.

At the REST API level, the Client class provides a consistent way to discover and traverse public resources, uses a generator to fetch all items using pagination, and automatically renews the bearer token.

Note that unlike HubInstance (from HubRestApi.py), Client does not provide a method for each and every endpoint. Instead it only provides a handful of core methods (Client API Reference) and then encourages the user to leverage the functionality provided by the underlying Requests session and to be comfortable working at that level. See Client versus HubInstance Comparison.

It is designed to provide a solid foundation especially suited for long-running scripts.

Table of Contents

Python 3 is required. Use pip to install the library from PyPI.

pip3 install blackduck

First generate an access token from Blackduck's UI: My Access Tokens

Save the token and provide it to Client. In the following example, the token is saved to an environment variable: blackduck_token

from blackduck import Client
import logging
import os
from pprint import pprint
logging.basicConfig(
level=logging.INFO,
format="[%(asctime)s] {%(module)s:%(lineno)d} %(levelname)s - %(message)s"
)
bd = Client(
token=os.environ.get('blackduck_token'),
base_url="https://your.blackduck.url",
# verify=False # TLS certificate verification
)

Blackduck's REST API provides a way to discover and traverse available resources identified by name. To take advantage of this mechanism, Client provides two key methods: list_resources() and get_resource()

To fetch what public resources are available, use the method list_resources() which returns a Python dict.

pprint(bd.list_resources()) # resources available at the root level

For an object returned from get_resource(), one can invoke list_resources() on it to see what sub-resources are available.

for project in bd.get_resource('projects'):
pprint(bd.list_resources(project))
quit(0)

To see the URL for the resource itself use the 'href' key:

resources_dict = bd.list_resources(project)
print(resources_dict['href'])

To fetch a resource pass in the name (str) provided by an earlier list_resources() call.

If the resource is paginated it will automatically return a generator that will fetch items as required and eventually will return all items. This generator can naturally be used in a for loop:

for project in bd.get_resource('projects'):
print(project['name'])

To fetch a sub-resource, pass in the parent. To get the versions for each project:

for project in bd.get_resource('projects'):
print(project['name'])
for version in bd.get_resource('versions', project):
print(version['versionName'])

If the resource is not paginated pass items=False and a dict will be returned:

bd.get_resource('maxSnippetFileSize', items=False)

This covers advanced topics that are relevant to a user more familiar with the REST API.

Parameters are set using 'params' of session.request. To fetch all users sorted by firstname:

params = {
'sort': 'firstname'
}
bd.get_resource('users', params=params) # constructs url /api/users?sort=firstname

It is possible to provide multiple values for the same key. To fetch all users with the letters 't' AND 'e' in the 'userName':

params = {
'q': ["userName:t", "userName:e"]
}
bd.get_resource('users', params=params) # constructs url: /api/users?q=userName:t&q=userName:e

A more complicated example to get all users with 'e' in the userName AND 'test' in the email, sorting the results by firstname:

params = {
'q': ["userName:e", "email:test"],
'sort': 'firstname',
}
bd.get_resource('users', params=params) # constructs url: /api/users?q=userName:e&q=email:test&sort=firstname

By default, if no 'accept' nor 'content-type' headers are specified with a GET request, the following default media type headers will be inserted:

headers = {
'accept': "application/json",
'content-type': "application/json"
}

Generally "application/json" instructs the REST API to use the most recent external version of an end-point. These headers will work with most (but unfortunately not all) endpoints.

To use a specific media type header specify one of 'accept', 'content-type' (or some combination of both) as required. For example:

headers = {
'accept': "application/vnd.blackducksoftware.user-4+json"
}
items = bd.get_resource('dormantUsers', headers=headers)

In addition to list_resources() and get_resource(), the Client purposely exposes the underlying requests layer so that the user can comfortably work with the request(), get()*, put(), post(), and delete() methods.

* While you can certainly use get() we recommend get_json() instead. See explanation further below.

Accessing the underlying session directly allows a user to customize the URL to send an arbitrary request. It is expected that the user provides any headers and handles the response object. See the very excellent Requests documentation. Requests is the #1 downloaded and used Python library for good reason and we encourage you to spend a few hours to learn more about it (if you haven't done so already) and leverage it when working with the REST API.

response = bd.session.get("/api/projects?offset=0&limit=10")
response.raise_for_status()
json_data = response.json()
items = json_data['items']

As the above construct of http get, check response, and decode the json to a dict is ubiquitous when fetching data, there is a helper method get_json() to streamline to a single call but preserve underlying error handling of HTTPErrors and JSONDecodeErrors.

json_data = bd.get_json("/api/projects?offset=0&limit=10")
items = json_data['items']

To see the request responses from the server, set the logging level to DEBUG

logging.basicConfig(
level=logging.DEBUG,
...
)
# DEBUG - https://your.blackduck.url:443 "GET /api/projects?offset=0&limit=100 HTTP/1.1" 200 None

Use the underlying session to POST data. The dict passed to session.post must use the named parameter 'json'.

For example:

project_data = {
'name': "test project",
'description': "some description",
'projectLevelAdjustments': True,
}
try:
r = bd.session.post("/api/projects", json=project_data)
r.raise_for_status()
print(f"created project {r.links['project']['url']}")
except requests.HTTPError as err:
# more fine grained error handling here; otherwise:
bd.http_error_handler(err)

or something like this:

r = bd.session.post("/api/projects", json=project_data)
if r.status_code == 201:
print(f"created project {r.links['project']['url']}")
else:
bd.http_error_handler(r)

Use the underlying session to PUT data. The dict passed to session.put must use the named parameter 'json'.

For example:

project_url = "/api/projects/c177cb7d-8942-484a-a203-9c2b0582c186" # for a specific project
project_data = {
'name': "test project",
'description': "some other description",
}
try:
r = bd.session.put(project_url, json=project_data)
r.raise_for_status()
print("updated project")
except requests.HTTPError as err:
if err.response.status_code == 404:
print("not found")
else:
bd.http_error_handler(err)

or something like this:

r = bd.session.put(project_url, json=project_data)
if r.status_code == 200:
print("updated project")
elif r.status_code == 404:
print("not found")
else:
bd.http_error_handler(r)

Use the underlying session to DELETE data. For example use this construct:

project_url = "/api/projects/c177cb7d-8942-484a-a203-9c2b0582c186" # for a specific project
try:
r = bd.session.delete(project_url)
r.raise_for_status()
print("deleted project")
except requests.HTTPError as err:
if err.response.status_code == 404:
print("not found")
else:
bd.http_error_handler(err)

or something like this:

r = bd.session.delete(project_url)
if r.status_code == 204:
print("deleted project")
elif r.status_code == 404:
print("not found")
else:
bd.http_error_handler(r)

If your code is already using a class named Client, use Python's import as to avoid a name clash:

from blackduck import Client as HubClient
bd = HubClient(
...
)

Username/password authentication is not recommended and it may be deprecated in the future. It is recommended to use the more secure access token authentication instead.

However, it is still possible to authenticate via username/password if required:

from blackduck import Client
from blackduck.Client import HubSession
from blackduck.Authentication import CookieAuth
import logging
logging.basicConfig(
level=logging.INFO,
format="[%(asctime)s] {%(module)s:%(lineno)d} %(levelname)s - %(message)s"
)
verify = False # TLS certificate verification
base_url = "https://your.blackduck.url"
session = HubSession(base_url, timeout=15.0, retries=3, verify=verify)
auth = CookieAuth(session, "sysadmin", "PASSWORD")
bd = Client(base_url=base_url, session=session, auth=auth)
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Client User Guide · blackducksoftware/hub-rest-api-python Wiki · GitHub
Skip to content

Client User Guide

Sumio Kiyooka edited this page May 13, 2021 · 26 revisions

Introduction

The Client class is a binding to Blackduck's REST API that provides a robust connection backed by a Requests session object. The user specifies a base URL, timeout, retries, proxies, and TLS verification upon initialization and these attributes are persisted across all requests.

At the REST API level, the Client class provides a consistent way to discover and traverse public resources, uses a generator to fetch all items using pagination, and automatically renews the bearer token.

Note that unlike HubInstance (from HubRestApi.py), Client does not provide a method for each and every endpoint. Instead it only provides a handful of core methods (Client API Reference) and then encourages the user to leverage the functionality provided by the underlying Requests session and to be comfortable working at that level. See Client versus HubInstance Comparison.

It is designed to provide a solid foundation especially suited for long-running scripts.

Table of Contents

Python 3 is required. Use pip to install the library from PyPI.

pip3 install blackduck

First generate an access token from Blackduck's UI: My Access Tokens

Save the token and provide it to Client. In the following example, the token is saved to an environment variable: blackduck_token

from blackduck import Client
import logging
import os
from pprint import pprint
logging.basicConfig(
level=logging.INFO,
format="[%(asctime)s] {%(module)s:%(lineno)d} %(levelname)s - %(message)s"
)
bd = Client(
token=os.environ.get('blackduck_token'),
base_url="https://your.blackduck.url",
# verify=False # TLS certificate verification
)

Blackduck's REST API provides a way to discover and traverse available resources identified by name. To take advantage of this mechanism, Client provides two key methods: list_resources() and get_resource()

To fetch what public resources are available, use the method list_resources() which returns a Python dict.

pprint(bd.list_resources()) # resources available at the root level

For an object returned from get_resource(), one can invoke list_resources() on it to see what sub-resources are available.

for project in bd.get_resource('projects'):
pprint(bd.list_resources(project))
quit(0)

To see the URL for the resource itself use the 'href' key:

resources_dict = bd.list_resources(project)
print(resources_dict['href'])

To fetch a resource pass in the name (str) provided by an earlier list_resources() call.

If the resource is paginated it will automatically return a generator that will fetch items as required and eventually will return all items. This generator can naturally be used in a for loop:

for project in bd.get_resource('projects'):
print(project['name'])

To fetch a sub-resource, pass in the parent. To get the versions for each project:

for project in bd.get_resource('projects'):
print(project['name'])
for version in bd.get_resource('versions', project):
print(version['versionName'])

If the resource is not paginated pass items=False and a dict will be returned:

bd.get_resource('maxSnippetFileSize', items=False)

This covers advanced topics that are relevant to a user more familiar with the REST API.

Parameters are set using 'params' of session.request. To fetch all users sorted by firstname:

params = {
'sort': 'firstname'
}
bd.get_resource('users', params=params) # constructs url /api/users?sort=firstname

It is possible to provide multiple values for the same key. To fetch all users with the letters 't' AND 'e' in the 'userName':

params = {
'q': ["userName:t", "userName:e"]
}
bd.get_resource('users', params=params) # constructs url: /api/users?q=userName:t&q=userName:e

A more complicated example to get all users with 'e' in the userName AND 'test' in the email, sorting the results by firstname:

params = {
'q': ["userName:e", "email:test"],
'sort': 'firstname',
}
bd.get_resource('users', params=params) # constructs url: /api/users?q=userName:e&q=email:test&sort=firstname

By default, if no 'accept' nor 'content-type' headers are specified with a GET request, the following default media type headers will be inserted:

headers = {
'accept': "application/json",
'content-type': "application/json"
}

Generally "application/json" instructs the REST API to use the most recent external version of an end-point. These headers will work with most (but unfortunately not all) endpoints.

To use a specific media type header specify one of 'accept', 'content-type' (or some combination of both) as required. For example:

headers = {
'accept': "application/vnd.blackducksoftware.user-4+json"
}
items = bd.get_resource('dormantUsers', headers=headers)

In addition to list_resources() and get_resource(), the Client purposely exposes the underlying requests layer so that the user can comfortably work with the request(), get()*, put(), post(), and delete() methods.

* While you can certainly use get() we recommend get_json() instead. See explanation further below.

Accessing the underlying session directly allows a user to customize the URL to send an arbitrary request. It is expected that the user provides any headers and handles the response object. See the very excellent Requests documentation. Requests is the #1 downloaded and used Python library for good reason and we encourage you to spend a few hours to learn more about it (if you haven't done so already) and leverage it when working with the REST API.

response = bd.session.get("/api/projects?offset=0&limit=10")
response.raise_for_status()
json_data = response.json()
items = json_data['items']

As the above construct of http get, check response, and decode the json to a dict is ubiquitous when fetching data, there is a helper method get_json() to streamline to a single call but preserve underlying error handling of HTTPErrors and JSONDecodeErrors.

json_data = bd.get_json("/api/projects?offset=0&limit=10")
items = json_data['items']

To see the request responses from the server, set the logging level to DEBUG

logging.basicConfig(
level=logging.DEBUG,
...
)
# DEBUG - https://your.blackduck.url:443 "GET /api/projects?offset=0&limit=100 HTTP/1.1" 200 None

Use the underlying session to POST data. The dict passed to session.post must use the named parameter 'json'.

For example:

project_data = {
'name': "test project",
'description': "some description",
'projectLevelAdjustments': True,
}
try:
r = bd.session.post("/api/projects", json=project_data)
r.raise_for_status()
print(f"created project {r.links['project']['url']}")
except requests.HTTPError as err:
# more fine grained error handling here; otherwise:
bd.http_error_handler(err)

or something like this:

r = bd.session.post("/api/projects", json=project_data)
if r.status_code == 201:
print(f"created project {r.links['project']['url']}")
else:
bd.http_error_handler(r)

Use the underlying session to PUT data. The dict passed to session.put must use the named parameter 'json'.

For example:

project_url = "/api/projects/c177cb7d-8942-484a-a203-9c2b0582c186" # for a specific project
project_data = {
'name': "test project",
'description': "some other description",
}
try:
r = bd.session.put(project_url, json=project_data)
r.raise_for_status()
print("updated project")
except requests.HTTPError as err:
if err.response.status_code == 404:
print("not found")
else:
bd.http_error_handler(err)

or something like this:

r = bd.session.put(project_url, json=project_data)
if r.status_code == 200:
print("updated project")
elif r.status_code == 404:
print("not found")
else:
bd.http_error_handler(r)

Use the underlying session to DELETE data. For example use this construct:

project_url = "/api/projects/c177cb7d-8942-484a-a203-9c2b0582c186" # for a specific project
try:
r = bd.session.delete(project_url)
r.raise_for_status()
print("deleted project")
except requests.HTTPError as err:
if err.response.status_code == 404:
print("not found")
else:
bd.http_error_handler(err)

or something like this:

r = bd.session.delete(project_url)
if r.status_code == 204:
print("deleted project")
elif r.status_code == 404:
print("not found")
else:
bd.http_error_handler(r)

If your code is already using a class named Client, use Python's import as to avoid a name clash:

from blackduck import Client as HubClient
bd = HubClient(
...
)

Username/password authentication is not recommended and it may be deprecated in the future. It is recommended to use the more secure access token authentication instead.

However, it is still possible to authenticate via username/password if required:

from blackduck import Client
from blackduck.Client import HubSession
from blackduck.Authentication import CookieAuth
import logging
logging.basicConfig(
level=logging.INFO,
format="[%(asctime)s] {%(module)s:%(lineno)d} %(levelname)s - %(message)s"
)
verify = False # TLS certificate verification
base_url = "https://your.blackduck.url"
session = HubSession(base_url, timeout=15.0, retries=3, verify=verify)
auth = CookieAuth(session, "sysadmin", "PASSWORD")
bd = Client(base_url=base_url, session=session, auth=auth)