Jubilant-backports, the joyful library for integration-testing charms in Juju 2.9 and above
Jubilant is a Python library that wraps the Juju CLI for use in charm integration tests. It provides methods that map 1:1 to Juju CLI commands, but with a type-annotated, Pythonic interface.
Jubilant-backports is a Python library that modifies Jubilant to provide support for running with Juju 2.9. It is a drop-in replacement for Jubilant, using the regular Jubilant package code for Juju 3.0 and above, as well as when there are no differences between 2.9 and higher, and custom code from a Jubilant branch otherwise. It supports the Jubilant 1.4 feature set, and future improvements to Jubilant are unlikely to be backported to Jubilant-backports (but security fixes and bug fixes, where required, will be backported).
You should consider using Jubilant-backports if you need to run the same charm integration test suite against Juju 2.9 as well as more modern Juju version.
Using Jubilant-backports
Jubilant-backports is published to PyPI, so you can install and use it with your favorite Python package manager:
$ pip install jubilant-backports
# or
$ uv add jubilant-backports
Because Jubilant-backports calls the Juju CLI, you'll also need to install Juju.
To use Jubilant-backports in Python code:
import jubilant_backports as jubilant
juju = jubilant.Juju()
juju.deploy('snappass-test')
juju.wait(jubilant.all_active)
# Or only wait for specific applications:
juju.wait(lambda status: jubilant.all_active(status, 'snappass-test', 'another-app'))
Note that we have done import jubilant_backports as jubilant and then used jubilant.Juju() rather than import jubilant_backports and jubilant_backports.Juju(). This will result in a simpler and cleaner change when you are ready to drop support for Juju 2.9, and want to move to the regular Jubilant package: all you need to do is change the dependency and the import statements, and the rest of your tests can be left unchanged.
Below is an example of a charm integration test. First we define a module-scoped pytest fixture named juju which creates a temporary model and runs the test with a Juju instance pointing at that model. Jubilant-backports's temp_model context manager creates the model during test setup and destroys it during teardown:
# conftest.py
@pytest.fixture(scope='module')
def juju():
with jubilant.temp_model() as juju:
yield juju
# test_deploy.py
def test_deploy(juju: jubilant.Juju): # Use the "juju" fixture # type: ignore
juju.deploy('snappass-test') # Deploy the charm
status = juju.wait(jubilant.all_active) # Wait till the app and unit are 'active'
# Hit the Snappass HTTP endpoint to ensure it's up and running.
address = status.apps['snappass-test'].units['snappass-test/0'].address
response = requests.get(f'http://{address}:5000/', timeout=10)
response.raise_for_status()
assert 'snappass' in response.text.lower()
You don't have to use pytest with Jubilant-backports, but it's what we recommend. Pytest's assert-based approach is a straight-forward way to write tests, and its fixtures are helpful for structuring setup and teardown.
Contributing and developing
Anyone can contribute to Jubilant-backports. The code lives in a branch of the regular Jubilant. It's best to start by opening an issue with a clear description of the problem or feature request, but you can also open a pull request directly.
Jubilant-backports uses uv to manage Python dependencies and tools, so you'll need to install uv to work on the library. You'll also need make to run local development tasks (but you probably have make installed already).
After that, clone the Jubilant codebase, check out the jubilant-backports branch, and use make all to run various checks and the unit tests:
$ git clone https://github.com/canonical/jubilant
Cloning into 'jubilant-backports'...
...
$ cd jubilant-backports
$ git checkout jubilant-backports
$ make all
...
========== 93 passed in 0.81s ==========
To contribute a code change, write your fix or feature, add tests and docs, then run make all before you push and create a PR. Once you create a PR, GitHub will also run the integration tests, which takes several minutes.
Doing a release
To create a new release of Jubilant-backports:
- Update the
__version__field injubilant_backports/__init__.pyto the new version you want to release. - Push up a PR with this change and get it reviewed and merged.
- Create a new release on GitHub with good release notes. The tag should start with a
backports-v, likebackports-v1.4.3. Once you've created the release, thepublish.yamlworkflow will automatically publish it to PyPI. - Once the publish workflow has finished, check that the new version appears in the PyPI version history.
Metadata
Release files for jubilant-backports 1.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| jubilant_backports-1.4.0.tar.gz | 30.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| jubilant_backports-1.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 60.4 kB
Release files / jubilant_backports-1.4.0.tar.gz
| Download URL | jubilant_backports-1.4.0.tar.gz |
|---|---|
| Size | 30.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0961645e67a08e85b3371d6c386795d254527d0c107a355ca24bdbca3872b671
|
|
BLAKE2b-256 checksum How to use checksums |
068480d7189a59de1250af22bc1ec121111765a0b6b060cd1c645f8cbedd0d38
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 8, 2025.
Transparency logRelease files / jubilant_backports-1.4.0-py3-none-any.whl
| Download URL | jubilant_backports-1.4.0-py3-none-any.whl |
|---|---|
| Size | 30.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b5c2d9aca29b39543bbe45b3205e97dd22b60fca9f8e0e66885b71201f8127d5
|
|
BLAKE2b-256 checksum How to use checksums |
62214fc19415293109bf49f7e7a4189ea4629770c228444a0b00c9e4e4270bb0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 8, 2025.
Transparency log