pulp_smash.api

Location: Pulp SmashAPI Documentationpulp_smash.api

A client for working with Pulp’s API.

Working with an API can require repetitive calls to perform actions like check HTTP status codes. In addition, Pulp’s API has specific quirks surrounding its handling of href paths and HTTP 202 status codes. This module provides a customizable client that makes it easier to work with the API in a safe and concise manner.

class pulp_smash.api.Client(server_config, response_handler=None, request_kwargs=None, pulp_system=None)

A convenience object for working with an API.

This class is a wrapper around the requests.api module provided by Requests. Each of the functions from that module are exposed as methods here, and each of the arguments accepted by Requests’ functions are also accepted by these methods. The difference between this class and the Requests functions lies in its configurable request and response handling mechanisms.

This class is flexible enough that it should be usable with any API, but certain defaults have been set to work well with Pulp.

As an example of basic usage, let’s say that you’d like to create a user, then read that user’s information back from the server. This is one way to do it:

>>> from pulp_smash.api import Client
>>> from pulp_smash.config import get_config
>>> client = Client(get_config())
>>> response = client.post('/pulp/api/v2/users/', {'login': 'Alice'})
>>> response = client.get(response.json()['_href'])
>>> print(response.json())

Notice how we never call response.raise_for_status()? We don’t need to because, by default, Client instances do this. Handy!

How does this work? Each Client object has a callback function, response_handler, that is given a chance to munge each server response. How else might this callback be useful? Well, notice how we call json() on each server response? That’s kludgy. Let’s write our own callback that takes care of this for us:

>>> from pulp_smash.api import Client
>>> from pulp_smash.config import get_config
>>> def response_handler(server_config, response):
...     response.raise_for_status()
...     return response.json()
>>> client = Client(get_config(), response_handler=response_handler)
>>> response = client.post('/pulp/api/v2/users/', {'login': 'Alice'})
>>> response = client.get(response['_href'])
>>> print(response)

Pulp Smash ships with several response handlers. See:

As mentioned, this class has configurable request and response handling mechanisms. We’ve covered response handling mechanisms — let’s move on to request handling mechanisms.

When a client is instantiated, a pulp_smash.config.PulpSmashConfig must be passed to the constructor, and configuration options are copied from the PulpSmashConfig to the client. These options can be overridden on a per-object or per-request basis. Here’s an example:

>>> from pulp_smash.api import Client
>>> from pulp_smash.config import PulpSmashConfig
>>> cfg = config.PulpSmashConfig(
...     pulp_auth=('username', 'password'),
...     systems=[
...         config.PulpSystem(
...             hostname='example.com',
...             roles={'api': {
...                'scheme': 'https',
...                'verify': '~/Documents/my.crt',
...             }}
...         )
...     ]
... )
>>> client = api.Client(cfg)
>>> client.request_kwargs['url'] == 'https://example.com'
True
>>> client.request_kwargs['verify'] == '~/Documents/my.crt'
True
>>> response = client.get('/index.html')  # Use my.crt for SSL verification
>>> response = client.get('/index.html', verify=False)  # Disable SSL
>>> response = client.get('/index.html')  # Use my.crt for SSL verification
>>> client.request_kwargs['verify'] = None
>>> response = client.get('/index.html')  # Do default SSL verification

Anything accepted by the Requests functions may be placed in request_kwargs or passed in to a specific call. You can set verify for example.

The url argument is slightly special. When making a call, it is possible to pass in a relative URL, as shown in the examples above. (e.g. /index.html.) The default URL and passed-in URL are joined like so:

>>> urljoin(request_kwargs['url'], passed_in_url)

This allows one to easily use the hrefs returned by Pulp in constructing new requests.

The remainder of this docstring contains design notes. They are useful to advanced users and developers.

Requests’ requests.api.post method has the following signature:

def post(url, data=None, json=None, **kwargs)

Pulp supports only JSON for most of its API endpoints, so it makes sense for us to demote data to being a regular kwarg and list json as the one and only positional argument.

We make json a positional argument for post(), put() and patch(), but not the other methods. Why? Because HTTP OPTIONS, GET, HEAD and DELETE must not have bodies. This is stated by the HTTP/1.1 specification, and network intermediaries such as caches are at liberty to drop such bodies.

Why the sentinel? Imagine the following scenario: a user provides a default JSON payload in self.request_kwargs, but they want to skip sending that payload for just one request. How can they do that? With client.post(url, None).

delete(url, **kwargs)

Send an HTTP DELETE request.

get(url, **kwargs)

Send an HTTP GET request.

head(url, **kwargs)

Send an HTTP HEAD request.

options(url, **kwargs)

Send an HTTP OPTIONS request.

patch(url, json=<object object>, **kwargs)

Send an HTTP PATCH request.

post(url, json=<object object>, **kwargs)

Send an HTTP POST request.

put(url, json=<object object>, **kwargs)

Send an HTTP PUT request.

request(method, url, **kwargs)

Send an HTTP request.

Arguments passed directly in to this method override (but do not overwrite!) arguments specified in self.request_kwargs.

pulp_smash.api.echo_handler(server_config, response)

Immediately return response.

pulp_smash.api.json_handler(server_config, response)

Like safe_handler, but also return a JSON-decoded response body.

Do what pulp_smash.api.safe_handler() does. In addition, decode the response body as JSON and return the result.

pulp_smash.api.poll_spawned_tasks(server_config, call_report, pulp_system=None)

Recursively wait for spawned tasks to complete. Yield response bodies.

Recursively wait for each of the spawned tasks listed in the given call report to complete. For each task that completes, yield a response body representing that task’s final state.

Parameters:
  • server_config – A pulp_smash.config.PulpSmashConfig object.
  • call_report – A dict-like object with a call report structure.
  • pulp_system – The system from where to pool the task. If None is provided then the first system found with api role will be used.
Returns:

A generator yielding task bodies.

Raises:

Same as poll_task().

pulp_smash.api.poll_task(server_config, href, pulp_system=None)

Wait for a task and its children to complete. Yield response bodies.

Poll the task at href, waiting for the task to complete. When a response is received indicating that the task is complete, yield that response body and recursively poll each child task.

Parameters:
  • server_config – A pulp_smash.config.PulpSmashConfig object.
  • href – The path to a task you’d like to monitor recursively.
  • pulp_system – The system from where to pool the task. If None is provided then the first system found with api role will be used.
Returns:

An generator yielding response bodies.

Raises:

pulp_smash.exceptions.TaskTimedOutError – If a task takes too long to complete.

pulp_smash.api.safe_handler(server_config, response)

Check status code, wait for tasks to complete, and check tasks.

Inspect the response’s HTTP status code. If the response has an HTTP Accepted status code, inspect the returned call report, wait for each task to complete, and inspect each completed task.

Raises:

requests.exceptions.HTTPError if the response status code is in the 4XX or 5XX range.

Raises: