{"id":9851,"library":"jubilant","title":"Jubilant","description":"Jubilant is a Python library that provides a high-level, asynchronous wrapper around the Juju CLI, primarily designed for integration testing of Juju charms. It simplifies programmatic interactions with Juju controllers and models, allowing for automation of deployments, configurations, and status checks. The library is actively maintained by Canonical, with frequent minor releases, and is currently at version 1.8.0.","status":"active","version":"1.8.0","language":"en","source_language":"en","source_url":"https://github.com/canonical/jubilant","tags":["juju","cli","testing","devops","charm","asynchronous","automation"],"install":[{"cmd":"pip install jubilant","lang":"bash","label":"Install stable version"}],"dependencies":[],"imports":[{"note":"The primary class for interacting with the Juju CLI and its commands.","symbol":"Juju","correct":"from jubilant import Juju"},{"note":"Use this import pattern if you installed 'jubilant-backports' for Juju 2.9 compatibility, allowing you to use the API as if it were the standard 'jubilant' package.","symbol":"jubilant_backports","correct":"import jubilant_backports as jubilant"}],"quickstart":{"code":"import asyncio\nfrom jubilant import Juju\n\nasync def main():\n    juju = Juju()\n    model_name = \"my-temp-test-model\"\n    print(f\"Attempting to add Juju model: {model_name}\")\n    try:\n        # Add a temporary model. For real tests, consider juju.temp_model()\n        # which uses a context manager for automatic cleanup.\n        await juju.add_model(model_name)\n        print(f\"Successfully added model '{model_name}'. Deploying charm...\")\n        \n        # Deploy a simple charm\n        await juju.deploy(\"ch:ubuntu\", model=model_name)\n        print(\"Ubuntu charm deployment initiated.\")\n        \n        # Wait for the charm to be active\n        await juju.wait_for_idle(apps=[\"ubuntu\"], model=model_name)\n        print(\"Ubuntu charm is active and idle.\")\n        \n        status = await juju.get_status(model=model_name)\n        print(f\"Model '{model_name}' status: {status.model.status}\")\n        \n    except Exception as e:\n        print(f\"An error occurred during Juju operations: {e}\")\n    finally:\n        print(f\"Attempting to destroy Juju model: {model_name}\")\n        await juju.destroy_model(model_name)\n        print(f\"Model '{model_name}' destroyed.\")\n\nif __name__ == \"__main__\":\n    # This example requires the Juju CLI to be installed and configured\n    # on your system, and supports Python 3.8+.\n    asyncio.run(main())\n","lang":"python","description":"This quickstart demonstrates how to initialize the `Juju` client, add a temporary Juju model, deploy a charm, wait for its status, and then destroy the model. This example is asynchronous and requires the 'juju' CLI to be installed and configured on your system. Using `Juju.temp_model()` as a context manager is generally preferred for testing scenarios to ensure cleanup."},"warnings":[{"fix":"Review all calls to `Juju.offer()` in your codebase. Explicitly provide the `model` argument (e.g., `model=my_model_name`) if the offer needs to target a specific model that is not the default for the `Juju` instance.","message":"The `Juju.offer()` method's API was updated in v1.8.0 to explicitly respect `self.model`. If your code previously relied on implicit model targeting behavior when calling `offer()`, it might now target a different model or require an explicit `model` argument to ensure the correct context.","severity":"breaking","affected_versions":">=1.8.0"},{"fix":"Install the Juju CLI (e.g., `snap install juju --classic` on Linux, or follow official Juju documentation for other platforms) and ensure its executable is in your system's PATH. You can verify the installation by running `juju --version` in your terminal.","message":"Jubilant acts as a programmatic wrapper around the Juju command-line interface (CLI). For Jubilant to function, the `juju` CLI must be installed on your system and accessible via the system's PATH. This is an essential external dependency, not a Python package managed by pip.","severity":"gotcha","affected_versions":"All versions"},{"fix":"Instead of `pip install jubilant`, use `pip install jubilant-backports`. In your Python code, change your imports from `from jubilant import Juju` to `import jubilant_backports as jubilant` (or `from jubilant_backports import Juju`) to maintain API compatibility.","message":"If you need to interact with Juju 2.9 controllers, you must use the `jubilant-backports` package instead of the standard `jubilant` package. `jubilant-backports` is specifically designed for Juju 2.9 compatibility, while `jubilant` targets Juju 3.x+.","severity":"gotcha","affected_versions":"All versions (when targeting Juju 2.9)"},{"fix":"Review any code utilizing `all_*` or `any_*` helpers. If your logic previously assumed these helpers would exclude subordinate units, you may need to adjust your expectations or filtering mechanisms.","message":"In v1.2.0, the `all_*` and `any_*` helper methods (e.g., `jubilant.all_active`) were updated to correctly include subordinate units in their evaluations. While considered a bug fix, this behavioral change might affect tests or logic that implicitly relied on subordinate units being excluded.","severity":"gotcha","affected_versions":">=1.2.0"}],"env_vars":null,"last_verified":"2026-04-17T00:00:00.000Z","next_check":"2026-07-16T00:00:00.000Z","problems":[{"fix":"Open your terminal or command prompt and run `pip install jubilant` to install the library.","cause":"The 'jubilant' Python package has not been installed in your current Python environment.","error":"ModuleNotFoundError: No module named 'jubilant'"},{"fix":"Install the Juju CLI according to the official Juju documentation for your operating system (e.g., `snap install juju --classic` on Linux), and ensure its installation directory is added to your system's PATH environment variable. Verify by running `juju --version` in a new terminal session.","cause":"Jubilant could not find the 'juju' executable. This indicates the Juju CLI is either not installed or not configured correctly in your system's environment PATH.","error":"jubilant.errors.JujuError: Command 'juju' not found in PATH. Make sure Juju CLI is installed."},{"fix":"Carefully check the names of applications, models, and other Juju entities used in your Jubilant calls. Ensure they match your Juju environment exactly and that the Juju controller and model are in the expected operational state before the call is made.","cause":"This error, originating from the underlying Juju CLI, usually means that an application, model, or other Juju resource specified in your Jubilant code does not exist, is misspelled, or is not in the expected state within the targeted Juju model.","error":"juju status --format json: application 'my-app' not found (stderr: ) (exit code 1)"},{"fix":"Ensure you are running your Python code with Python 3.8 or a newer version to support the required syntax and `asyncio` features.","cause":"Jubilant is built on Python's `asyncio` framework and uses `async/await` syntax. `asyncio.run()` also requires Python 3.7+. Jubilant itself requires Python 3.8+.","error":"SyntaxError: invalid syntax"}]}