ops.testing (was: Scenario)¶
Install ops with the testing
extra to use this API; for example:
pip install ops[testing]
State-transition tests, previously known as ‘Scenario’, expect you to define the Juju state all at once, define the Juju context against which to test the charm, and fire a single event on the charm to execute its logic. The tests can then assert that the Juju state has changed as expected.
A very simple test, where the charm has no config, no integrations, the unit is the leader, and has a start handler that sets the status to active might look like this:
from ops import testing
def test_base():
ctx = testing.Context(MyCharm)
state = testing.State(leader=True)
out = ctx.run(ctx.on.start(), state)
assert out.unit_status == testing.ActiveStatus()
These ‘state-transition’ tests give charm authors a way to test how the state changes in reaction to events. They are not necessarily tests of individual methods or functions; they are testing the ‘contract’ of the charm: given a certain state, when a certain event happens, the charm should transition to another state. Unlike integration tests, they do not test using a real Juju controller and model, and focus on a single Juju unit. For simplicity, we refer to them as ‘unit’ tests.
Writing these tests should nudge you into thinking of a charm as a black-box ‘input to output’ function. The inputs are:
Event: why am I, the charm, being executed
State: am I the leader? what is my integration data? what is my config?
Context: what integrations can I have? what containers can I have?
The output is another State: the state after the charm has interacted with the mocked Juju model. The output state is the same type of data structure as the input state.

Writing unit tests for a charm, then, means verifying that:
the output state (as compared with the input state) is as expected
the charm does not raise uncaught exceptions while handling the event
A test consists of three broad steps:
- Arrange:
declare the context
declare the input state
- Act:
run an event (ie. obtain the output state, given the input state and the event)
- Assert:
verify that the output state is what you expect it to be
verify that the charm has seen a certain sequence of statuses, events, and juju-log calls
Note
Unit testing is only one aspect of a comprehensive testing strategy. For more on testing charms, see Charm SDK | Testing.
- class ops.testing.ActionFailed(message: str, output: ActionOutput | None = None, *, state: State | None = None)[source]¶
Bases:
Exception
Raised when
event.fail()
is called during an action handler.- message: str¶
Optional details of the failure, as provided by
ops.ActionEvent.fail()
.
- output: ActionOutput¶
Any logs and results set by the Charm.
When using Context.run, both logs and results will be empty - these can be found in Context.action_logs and Context.action_results.
- class ops.testing.ActiveStatus(message: str = '')[source]¶
Bases:
_EntityStatus
,ActiveStatus
The unit or application is ready and active.
Set this status when the charm is correctly offering all the services it has been asked to offer. If the unit or application is operational but some feature (like high availability) is in a degraded state, set “active” with an appropriate message.
- name: Literal['active'] = 'active'¶
- class ops.testing.Address(value: str, *, hostname: str = '', cidr: str = '')[source]¶
Bases:
_MaxPositionalArgs
An address in a Juju network space.
- class ops.testing.BindAddress(addresses: list[Address], *, interface_name: str = '', mac_address: str | None = None)[source]¶
Bases:
_MaxPositionalArgs
An address bound to a network interface in a Juju space.
- class ops.testing.BlockedStatus(message: str = '')[source]¶
Bases:
_EntityStatus
,BlockedStatus
The unit or application requires manual intervention.
Set this status when an administrator has to manually intervene to unblock the charm to let it proceed.
- name: Literal['blocked'] = 'blocked'¶
- class ops.testing.CharmEvents[source]¶
Bases:
object
Events generated by Juju or ops pertaining to the application lifecycle.
The events listed as attributes of this class should be accessed via the
Context.on
attribute. For example:ctx.run(ctx.on.config_changed(), state)
This behaves similarly to the
ops.CharmEvents
class but is much simpler as there are no dynamically named attributes, and no__getattr__
version to get events. In addition, all of the attributes are methods, which are used to connect the event to the specific object that they relate to (or, for simpler events like “start” or “stop”, take no arguments).- static action(name: str, params: Mapping[str, AnyJson] | None = None, id: str | None = None)[source]¶
Events raised by Juju when an administrator invokes a Juju Action.
This class is the data type of events triggered when an administrator invokes a Juju Action. Callbacks bound to these events may be used for responding to the administrator’s Juju Action request.
To read the parameters for the action, see the instance variable
params
. To respond with the result of the action, callset_results()
. To add progress messages that are visible as the action is progressing uselog()
.
- static collect_app_status()[source]¶
Event triggered at the end of every hook to collect app statuses for evaluation
- static collect_unit_status()[source]¶
Event triggered at the end of every hook to collect unit statuses for evaluation
- static config_changed()[source]¶
Event triggered when a configuration change occurs.
This event will fire in several situations:
When the admin reconfigures the charm using the Juju CLI, for example
juju config mycharm foo=bar
. This event notifies the charm of its new configuration. (The event itself, however, is not aware of what specifically has changed in the config).Right after the unit starts up for the first time. This event notifies the charm of its initial configuration. Typically, this event will fire between an
InstallEvent
and a :class:~`ops.StartEvent` during the startup sequence (when a unit is first deployed), but in general it will fire whenever the unit is (re)started, for example after pod churn on Kubernetes, on unit rescheduling, on unit upgrade or refresh, and so on.As a specific instance of the above point: when networking changes (if the machine reboots and comes up with a different IP).
When the app config changes, for example when juju trust is run.
Any callback method bound to this event cannot assume that the software has already been started; it should not start stopped software, but should (if appropriate) restart running software to take configuration changes into account.
- static install()[source]¶
Event triggered when a charm is installed.
This event is triggered at the beginning of a charm’s lifecycle. Any associated callback method should be used to perform one-time setup operations, such as installing prerequisite software.
- static leader_elected()[source]¶
Event triggered when a new leader has been elected.
Juju will trigger this event when a new leader unit is chosen for a given application.
This event fires at least once after Juju selects a leader unit. Callback methods bound to this event may take any action required for the elected unit to assert leadership. Note that only the elected leader unit will receive this event.
- static pebble_check_failed(container: Container, info: CheckInfo)[source]¶
Event triggered when a Pebble check exceeds the configured failure threshold.
Note that the check may have started passing by the time this event is emitted (which will mean that a
PebbleCheckRecoveredEvent
will be emitted next). If the handler is executing code that should only be done if the check is currently failing, check the current status withevent.info.status == ops.pebble.CheckStatus.DOWN
.Added in Juju version 3.6.
- static pebble_check_recovered(container: Container, info: CheckInfo)[source]¶
Event triggered when a Pebble check recovers.
This event is only triggered when the check has previously reached a failure state (not simply failed, but failed at least as many times as the configured threshold).
Added in Juju version 3.6.
- static pebble_custom_notice(container: Container, notice: Notice)[source]¶
Event triggered when a Pebble notice of type “custom” is created or repeats.
Added in Juju version 3.4.
- static pebble_ready(container: Container)[source]¶
Event triggered when Pebble is ready for a workload.
This event is triggered when the Pebble process for a workload/container starts up, allowing the charm to configure how services should be launched.
Callback methods bound to this event allow the charm to run code after a workload has started its Pebble instance and is ready to receive instructions regarding what services should be started. The name prefix of the hook will depend on the container key defined in the
metadata.yaml
file.
- static post_series_upgrade()[source]¶
Event triggered after a series upgrade.
This event is triggered after the administrator has done a distribution upgrade (or rolled back and kept the same series). It is called in response to
juju upgrade-machine <machine> complete
. Associated charm callback methods are expected to do whatever steps are necessary to reconfigure their applications for the new series. This may include things like populating the upgraded version of a database. Note however charms are expected to check if the series has actually changed or whether it was rolled back to the original series.Scheduled for removal in Juju version 4.0.
- static pre_series_upgrade()[source]¶
Event triggered to prepare a unit for series upgrade.
This event triggers when an administrator executes
juju upgrade-machine <machine> prepare
. The event will fire for each unit that is running on the specified machine. Any callback method bound to this event must prepare the charm for an upgrade to the series. This may include things like exporting database content to a version neutral format, or evacuating running instances to other machines.It can be assumed that only after all units on a machine have executed the callback method associated with this event, the administrator will initiate steps to actually upgrade the series. After the upgrade has been completed, the
PostSeriesUpgradeEvent
will fire.Scheduled for removal in Juju version 4.0.
- static relation_broken(relation: RelationBase)[source]¶
Event triggered when a relation is removed.
If a relation is being removed (
juju remove-relation
orjuju remove-application
), once all the units have been removed, this event will fire to signal that the relationship has been fully terminated.The event indicates that the current relation is no longer valid, and that the charm’s software must be configured as though the relation had never existed. It will only be called after every callback method bound to
RelationDepartedEvent
has been run. If a callback method bound to this event is being executed, it is guaranteed that no remote units are currently known locally.
- static relation_changed(relation: RelationBase, *, remote_unit: int | None = None)[source]¶
Event triggered when relation data changes.
This event is triggered whenever there is a change to the data bucket for a related application or unit. Look at
event.relation.data[event.unit/app]
to see the new information, whereevent
is the event object passed to the callback method bound to this event.This event always fires once, after
RelationJoinedEvent
, and will subsequently fire whenever that remote unit changes its data for the relation. Callback methods bound to this event should be the only ones that rely on remote relation data. They should not error if the data is incomplete, since it can be guaranteed that when the remote unit or application changes its data, the event will fire again.The data that may be queried, or set, are determined by the relation’s interface.
- static relation_created(relation: RelationBase)[source]¶
Event triggered when a new relation is created.
This is triggered when a new integration with another app is added in Juju. This can occur before units for those applications have started. All existing relations will trigger RelationCreatedEvent before
StartEvent
is emitted.
- static relation_departed(relation: RelationBase, *, remote_unit: int | None = None, departing_unit: int | None = None)[source]¶
Event triggered when a unit leaves a relation.
This is the inverse of the
RelationJoinedEvent
, representing when a unit is leaving the relation (the unit is being removed, the app is being removed, the relation is being removed). For remaining units, this event is emitted once for each departing unit. For departing units, this event is emitted once for each remaining unit.Callback methods bound to this event may be used to remove all references to the departing remote unit, because there’s no guarantee that it’s still part of the system; it’s perfectly probable (although not guaranteed) that the system running that unit has already shut down.
Once all callback methods bound to this event have been run for such a relation, the unit agent will fire the
RelationBrokenEvent
.
- static relation_joined(relation: RelationBase, *, remote_unit: int | None = None)[source]¶
Event triggered when a new unit joins a relation.
This event is triggered whenever a new unit of a related application joins the relation. The event fires only when that remote unit is first observed by the unit. Callback methods bound to this event may set any local unit data that can be determined using no more than the name of the joining unit and the remote
private-address
setting, which is always available when the relation is created and is by convention not deleted.
- static remove()[source]¶
Event triggered when a unit is about to be terminated.
This event fires prior to Juju removing the charm and terminating its unit.
- static secret_changed(secret: Secret)[source]¶
Event triggered on the secret observer charm when the secret owner changes its contents.
When the owner of a secret changes the secret’s contents, Juju will create a new secret revision, and all applications or units that are tracking this secret will be notified via this event that a new revision is available.
Typically, the charm will fetch the new content by calling
event.secret.get_content()
withrefresh=True
to tell Juju to start tracking the new revision.Added in Juju version 3.0: Charm secrets added in Juju 3.0, user secrets added in Juju 3.3
- static secret_expired(secret: Secret, *, revision: int)[source]¶
Event triggered on the secret owner charm when a secret’s expiration time elapses.
This event is fired on the secret owner to inform it that the secret revision must be removed. The event will keep firing until the owner removes the revision by calling
event.secret.remove_revision()
.Added in Juju version 3.0.
- static secret_remove(secret: Secret, *, revision: int)[source]¶
Event triggered on the secret owner charm when a secret revision can be removed.
When the owner of a secret creates a new revision, and after all observers have updated to that new revision, this event will be fired to inform the secret owner that the old revision can be removed.
After any required cleanup, the charm should call
event.secret.remove_revision()
to remove the now-unused revision. If the charm does not, then the event will be emitted again, when further revisions are ready for removal.Added in Juju version 3.0.
- static secret_rotate(secret: Secret)[source]¶
Event triggered on the secret owner charm when the secret’s rotation policy elapses.
This event is fired on the secret owner to inform it that the secret must be rotated. The event will keep firing until the owner creates a new revision by calling
event.secret.set_content()
.Added in Juju version 3.0.
- static start()[source]¶
Event triggered immediately after first configuration change.
This event is triggered immediately after the first
ConfigChangedEvent
. Callback methods bound to the event should be used to ensure that the charm’s software is in a running state. Note that the charm’s software should be configured so as to persist in this state through reboots without further intervention on Juju’s part.
- static stop()[source]¶
Event triggered when a charm is shut down.
This event is triggered when an application’s removal is requested by the client. The event fires immediately before the end of the unit’s destruction sequence. Callback methods bound to this event should be used to ensure that the charm’s software is not running, and that it will not start again on reboot.
- static storage_attached(storage: Storage)[source]¶
Event triggered when new storage becomes available.
This event is triggered when new storage is available for the charm to use.
Callback methods bound to this event allow the charm to run code when storage has been added. Such methods will be run before the
InstallEvent
fires, so that the installation routine may use the storage. The name prefix of this hook will depend on the storage key defined in themetadata.yaml
file.
- static storage_detaching(storage: Storage)[source]¶
Event triggered prior to removal of storage.
This event is triggered when storage a charm has been using is going away.
Callback methods bound to this event allow the charm to run code before storage is removed. Such methods will be run before storage is detached, and always before the
StopEvent
fires, thereby allowing the charm to gracefully release resources before they are removed and before the unit terminates. The name prefix of the hook will depend on the storage key defined in themetadata.yaml
file.
- static update_status()[source]¶
Event triggered by a status update request from Juju.
This event is periodically triggered by Juju so that it can provide constant feedback to the administrator about the status of the application the charm is modeling. Any callback method bound to this event should determine the “health” of the application and set the status appropriately.
The interval between
UpdateStatusEvent
events can be configured model-wide, e.g.juju model-config update-status-hook-interval=1m
.
- static upgrade_charm()[source]¶
Event triggered by request to upgrade the charm.
This event will be triggered when an administrator executes
juju upgrade-charm
. The event fires after Juju has unpacked the upgraded charm code, and so this event will be handled by the callback method bound to the event in the new codebase. The associated callback method is invoked provided there is no existing error state. The callback method should be used to reconcile current state written by an older version of the charm into whatever form that is needed by the current charm version.
- class ops.testing.CheckInfo(name: str, *, level: pebble.CheckLevel | None = None, status: pebble.CheckStatus = CheckStatus.UP, failures: int = 0, threshold: int = 3)[source]¶
Bases:
_MaxPositionalArgs
A health check for a Pebble workload container.
- level: CheckLevel | None = None¶
Level of the check.
- status: CheckStatus = 'up'¶
Status of the check.
ops.pebble.CheckStatus.UP
means the check is healthy (the number of failures is fewer than the threshold),ops.pebble.CheckStatus.DOWN
means the check is unhealthy (the number of failures has reached the threshold).
- class ops.testing.CloudCredential(*, auth_type: str, attributes: dict[str, str] = <factory>, redacted: list[str] = <factory>)[source]¶
Bases:
_MaxPositionalArgs
Credentials for cloud.
Used as the type of attribute credential in
CloudSpec
.
- class ops.testing.CloudSpec(type: str, *, name: str = 'localhost', region: str | None = None, endpoint: str | None = None, identity_endpoint: str | None = None, storage_endpoint: str | None = None, credential: CloudCredential | None = None, ca_certificates: list[str] = <factory>, skip_tls_verify: bool = False, is_controller_cloud: bool = False)[source]¶
Bases:
_MaxPositionalArgs
Cloud specification information (metadata) including credentials.
- credential: CloudCredential | None = None¶
Cloud credentials with key-value attributes.
- class ops.testing.Container(name: str, *, can_connect: bool = False, _base_plan: dict[str, Any] = <factory>, layers: dict[str, pebble.Layer] = <factory>, service_statuses: dict[str, pebble.ServiceStatus] = <factory>, mounts: dict[str, Mount] = <factory>, execs: Iterable[Exec] = frozenset({}), notices: list[Notice] = <factory>, check_infos: frozenset[CheckInfo] = frozenset({}))[source]¶
Bases:
_MaxPositionalArgs
A Kubernetes container where a charm’s workload runs.
- check_infos: frozenset[CheckInfo] = frozenset({})¶
All Pebble health checks that have been added to the container.
- execs: Iterable[Exec] = frozenset({})¶
Simulate executing commands in the container.
Specify each command the charm might run in the container and an
Exec
containing its return code and any stdout/stderr.For example:
container = Container( name='foo', execs={ Exec(['whoami'], return_code=0, stdout='ubuntu'), Exec( ['dig', '+short', 'canonical.com'], return_code=0, stdout='185.125.190.20\n185.125.190.21', ), } )
- get_filesystem(ctx: Context) pathlib.Path [source]¶
Simulated Pebble filesystem in this context.
- Returns:
A temporary filesystem containing any files or directories the charm pushed to the container.
- layers: dict[str, Layer]¶
All
ops.pebble.Layer
definitions that have already been added to the container.
- mounts: dict[str, Mount]¶
Provides access to the contents of the simulated container filesystem.
For example, suppose you want to express that your container has:
/home/foo/bar.py
/bin/bash
/bin/baz
this becomes:
mounts = { 'foo': Mount('/home/foo', pathlib.Path('/path/to/local/dir/containing/bar/py/')), 'bin': Mount('/bin/', pathlib.Path('/path/to/local/dir/containing/bash/and/baz/')), }
- property plan: Plan¶
The ‘computed’ Pebble plan.
This is the base plan plus the layers that have been added on top. You should run your assertions on this plan, not so much on the layers, as those are input data.
- service_statuses: dict[str, ServiceStatus]¶
The current status of each Pebble service running in the container.
- property services: dict[str, ServiceInfo]¶
The Pebble services as rendered in the plan.
- class ops.testing.Context(charm_type: type[CharmType], meta: dict[str, Any] | None = None, *, actions: dict[str, Any] | None = None, config: dict[str, Any] | None = None, charm_root: str | Path | None = None, juju_version: str = '3.5', capture_deferred_events: bool = False, capture_framework_events: bool = False, app_name: str | None = None, unit_id: int | None = 0, app_trusted: bool = False)[source]¶
Bases:
Generic
[CharmType
]Represents a simulated charm’s execution context.
The main entry point to running a test. It contains:
the charm source code being executed
the metadata files associated with it
a charm project repository root
the Juju version to be simulated
After you have instantiated
Context
, typically you will callrun()
to execute the charm once, write any assertions you like on the output state returned by the call, write any assertions you like on theContext
attributes, then discard theContext
.Each
Context
instance is in principle designed to be single-use:Context
is not cleaned up automatically between charm runs.Any side effects generated by executing the charm, that are not rightful part of the
State
, are in fact stored in theContext
:This allows you to write assertions not only on the output state, but also, to some extent, on the path the charm took to get there.
A typical test will look like:
from charm import MyCharm, MyCustomEvent # noqa def test_foo(): # Arrange: set the context up ctx = Context(MyCharm) # Act: prepare the state and emit an event state_out = ctx.run(ctx.on.update_status(), State()) # Assert: verify the output state is what you think it should be assert state_out.unit_status == ActiveStatus('foobar') # Assert: verify the Context contains what you think it should assert len(c.emitted_events) == 4 assert isinstance(c.emitted_events[3], MyCustomEvent)
If you need access to the charm object that will handle the event, use the class in a
with
statement, like:def test_foo(): ctx = Context(MyCharm) with ctx(ctx.on.start(), State()) as manager: manager.charm._some_private_setup() manager.run()
- __call__(event: _Event, state: State) Manager[CharmType] [source]¶
Context manager to introspect live charm object before and after the event is emitted.
Usage:
ctx = Context(MyCharm) with ctx(ctx.on.start(), State()) as manager: manager.charm._some_private_setup() manager.run() # this will fire the event assert manager.charm._some_private_attribute == "bar" # noqa
- Parameters:
event – the event that the charm will respond to.
state – the
State
instance to use when handling the event.
- action_logs: list[str]¶
The logs associated with the action output, set by the charm with
ops.ActionEvent.log()
This will be empty when handling a non-action event.
- action_results: dict[str, Any] | None¶
A key-value mapping assigned by the charm as a result of the action.
This will be
None
if the charm never callsops.ActionEvent.set_results()
- emitted_events: list[ops.EventBase]¶
A record of the events (including custom) that the charm has processed
- juju_log: list[JujuLogLine]¶
A record of what the charm has sent to juju-log
- on: CharmEvents¶
The events that this charm can respond to.
Use this when calling
run()
to specify the event to emit.
- class ops.testing.DeferredEvent(handle_path: str, owner: str, observer: str, snapshot_data: dict[~typing.Any, ~typing.Any] = <factory>)[source]¶
Bases:
object
An event that has been deferred to run prior to the next Juju event.
Tests should not instantiate this class directly: use the deferred method of the event instead. For example:
ctx = Context(MyCharm) deferred_start = ctx.on.start().deferred(handler=MyCharm._on_start) state = State(deferred=[deferred_start])
- property name¶
A comparable name for the event.
- class ops.testing.ErrorStatus(message: str = '')[source]¶
Bases:
_EntityStatus
,ErrorStatus
The unit status is error.
The unit-agent has encountered an error (the application or unit requires human intervention in order to operate correctly).
This status is read-only; trying to set unit or application status to
ErrorStatus
will raiseInvalidStatusError
.- name: Literal['error'] = 'error'¶
- class ops.testing.Exec(command_prefix: Sequence[str], *, return_code: int = 0, stdout: str = '', stderr: str = '', _change_id: int = <factory>)[source]¶
Bases:
_MaxPositionalArgs
Mock data for simulated
ops.Container.exec()
calls.- return_code: int = 0¶
The return code of the process.
Use 0 to mock the process ending successfully, and other values for failure.
- class ops.testing.ICMPPort(*, port: int | None = None, protocol: _RawPortProtocolLiteral = 'icmp', _max_positional_args: Final = 0)[source]¶
Bases:
Port
Represents an ICMP port on the charm host.
- class ops.testing.JujuLogLine(level: str, message: str)[source]¶
Bases:
object
An entry in the Juju debug-log.
- class ops.testing.MaintenanceStatus(message: str = '')[source]¶
Bases:
_EntityStatus
,MaintenanceStatus
The unit or application is performing maintenance tasks.
Set this status when the charm is performing an operation such as
apt install
, or is waiting for something under its control, such aspebble-ready
or an exec operation in the workload container. In contrast toWaitingStatus
, “maintenance” reflects activity on this unit or charm, not on peers or related units.- name: Literal['maintenance'] = 'maintenance'¶
- class ops.testing.Manager(ctx: Context[CharmType], arg: _Event, state_in: State)[source]¶
Bases:
Generic
[CharmType
]Context manager to offer test code some runtime charm object introspection.
This class should not be instantiated directly: use a
Context
in awith
statement instead, for example:ctx = Context(MyCharm) with ctx(ctx.on.start(), State()) as manager: manager.charm.setup() manager.run()
- property charm: CharmType¶
The charm object instantiated by ops to handle the event.
The charm is only available during the context manager scope.
- class ops.testing.Model(name: str = <factory>, *, uuid: str = <factory>, type: Literal['kubernetes', 'lxd'] = 'kubernetes', cloud_spec: CloudSpec | None = None)[source]¶
Bases:
_MaxPositionalArgs
The Juju model in which the charm is deployed.
- class ops.testing.Mount(*, location: str | pathlib.PurePosixPath, source: str | pathlib.Path)[source]¶
Bases:
_MaxPositionalArgs
Maps local files to a
Container
filesystem.- location: str | PurePosixPath¶
The location inside of the container.
- source: str | Path¶
The content to provide when the charm does
ops.Container.pull()
.
- class ops.testing.Network(binding_name: str, bind_addresses: list[BindAddress] = <factory>, *, ingress_addresses: list[str] = <factory>, egress_subnets: list[str] = <factory>)[source]¶
Bases:
_MaxPositionalArgs
A Juju network space.
- bind_addresses: list[BindAddress]¶
Addresses that the charm’s application should bind to.
- class ops.testing.Notice(key: str, *, id: str = <factory>, user_id: int | None = None, type: pebble.NoticeType | str = NoticeType.CUSTOM, first_occurred: datetime.datetime = <factory>, last_occurred: datetime.datetime = <factory>, last_repeated: datetime.datetime = <factory>, occurrences: int = 1, last_data: dict[str, str] = <factory>, repeat_after: datetime.timedelta | None = None, expire_after: datetime.timedelta | None = None)[source]¶
Bases:
_MaxPositionalArgs
A Pebble notice.
- expire_after: timedelta | None = None¶
How long since one of these last occurred until Pebble will drop the notice.
- key: str¶
The notice key, a string that differentiates notices of this type.
This is in the format
domain/path
; for example:canonical.com/postgresql/backup
orexample.com/mycharm/notice
.
- last_data: dict[str, str]¶
Additional data captured from the last occurrence of one of these notices.
- last_repeated: datetime¶
The time this notice was last repeated.
See Pebble’s Notices documentation for an explanation of what “repeated” means.
- repeat_after: timedelta | None = None¶
Minimum time after one of these was last repeated before Pebble will repeat it again.
- type: NoticeType | str = 'custom'¶
Type of the notice.
- class ops.testing.PeerRelation(endpoint: str, interface: str | None = None, *, id: int = <factory>, local_app_data: RawDataBagContents = <factory>, local_unit_data: RawDataBagContents = <factory>, peers_data: dict[UnitID, RawDataBagContents] = <factory>)[source]¶
Bases:
RelationBase
A relation to share data between units of the charm.
- class ops.testing.Port(port: int | None = None, *, protocol: _RawPortProtocolLiteral = 'tcp')[source]¶
Bases:
_MaxPositionalArgs
Represents a port on the charm host.
Port objects should not be instantiated directly: use
TCPPort
,UDPPort
, orICMPPort
instead.
- class ops.testing.Relation(endpoint: str, interface: str | None = None, *, id: int = <factory>, local_app_data: RawDataBagContents = <factory>, local_unit_data: RawDataBagContents = <factory>, remote_app_name: str = 'remote', limit: int = 1, remote_app_data: RawDataBagContents = <factory>, remote_units_data: dict[UnitID, RawDataBagContents] = <factory>)[source]¶
Bases:
RelationBase
An integration between the charm and another application.
- class ops.testing.RelationBase(endpoint: str, interface: str | None = None, *, id: int = <factory>, local_app_data: RawDataBagContents = <factory>, local_unit_data: RawDataBagContents = <factory>)[source]¶
Bases:
_MaxPositionalArgs
Base class for the various types of integration (relation).
- id: int¶
Juju relation ID. Every new Relation instance gets a unique one, if there’s trouble, override.
- class ops.testing.Resource(*, name: str, path: str | pathlib.Path)[source]¶
Bases:
_MaxPositionalArgs
Represents a resource made available to the charm.
- class ops.testing.Secret(tracked_content: RawSecretRevisionContents, *, latest_content: RawSecretRevisionContents | None = None, id: str = <factory>, owner: Literal['unit', 'app', None] = None, remote_grants: dict[int, set[str]] = <factory>, label: str | None = None, description: str | None = None, expire: datetime.datetime | None = None, rotate: SecretRotate | None = None, _tracked_revision: int = 1, _latest_revision: int = 1)[source]¶
Bases:
_MaxPositionalArgs
A Juju secret.
This class is used for both user and charm secrets.
- id: str¶
The Juju ID of the secret.
This is automatically assigned and should not usually need to be explicitly set.
- label: str | None = None¶
A human-readable label the charm can use to retrieve the secret.
If this is set, it implies that the charm has previously set the label.
- latest_content: Dict[str, str] | None = None¶
The content of the latest revision of the secret.
This is the content the charm will receive with a
ops.Secret.peek_content()
call.
- owner: Literal['unit', 'app', None] = None¶
Indicates if the secret is owned by this unit, this application, or another application/unit.
If None, the implication is that read access to the secret has been granted to this unit.
- remote_grants: dict[int, set[str]]¶
Mapping from relation IDs to remote units and applications to which this secret has been granted.
- rotate: SecretRotate | None = None¶
The rotation policy for the secret.
- tracked_content: Dict[str, str]¶
The content of the secret that the charm is currently tracking.
This is the content the charm will receive with a
ops.Secret.get_content()
call.
- class ops.testing.State(*, config: dict[str, str | int | float | bool] = <factory>, relations: Iterable[RelationBase] = <factory>, networks: Iterable[Network] = <factory>, containers: Iterable[Container] = <factory>, storages: Iterable[Storage] = <factory>, opened_ports: Iterable[Port] = <factory>, leader: bool = False, model: Model = Model(name='04d5vC3YGYmwqpONWQIN', uuid='af83f61e-b5fe-4140-84a8-b3ba579b537c', type='kubernetes', cloud_spec=None), secrets: Iterable[Secret] = <factory>, resources: Iterable[Resource] = <factory>, planned_units: int = 1, deferred: list[DeferredEvent] = <factory>, stored_states: Iterable[StoredState] = <factory>, app_status: _EntityStatus = UnknownStatus(), unit_status: _EntityStatus = UnknownStatus(), workload_version: str = '')[source]¶
Bases:
_MaxPositionalArgs
Represents the Juju-owned portion of a unit’s state.
Roughly speaking, it wraps all hook-tool- and pebble-mediated data a charm can access in its lifecycle. For example, status-get will return data from State.unit_status, is-leader will return data from State.leader, and so on.
- app_status: _EntityStatus = UnknownStatus()¶
Status of the application.
- containers: Iterable[Container]¶
All containers (whether they can connect or not) that this charm is aware of.
- deferred: list[DeferredEvent]¶
Events that have been deferred on this charm by some previous execution.
- get_container(container: str, /) Container [source]¶
Get container from this State, based on its name.
- get_network(binding_name: str, /) Network [source]¶
Get network from this State, based on its binding name.
- get_relation(relation: int, /) RelationBase [source]¶
Get relation from this State, based on the relation’s id.
- get_relations(endpoint: str) tuple[RelationBase, ...] [source]¶
Get all relations on this endpoint from the current state.
- get_secret(*, id: str | None = None, label: str | None = None) Secret [source]¶
Get secret from this State, based on the secret’s id or label.
- get_storage(storage: str, /, *, index: int | None = 0) Storage [source]¶
Get storage from this State, based on the storage’s name and index.
- get_stored_state(stored_state: str, /, *, owner_path: str | None = None) StoredState [source]¶
Get stored state from this State, based on the stored state’s name and owner_path.
- model: Model = Model(name='04d5vC3YGYmwqpONWQIN', uuid='af83f61e-b5fe-4140-84a8-b3ba579b537c', type='kubernetes', cloud_spec=None)¶
The model this charm lives in.
- networks: Iterable[Network]¶
Manual overrides for any relation and extra bindings currently provisioned for this charm. If a metadata-defined relation endpoint is not explicitly mapped to a Network in this field, it will be defaulted.
Warning
extra-bindings is a deprecated, regretful feature in Juju/ops. For completeness we support it, but use at your own risk. If a metadata-defined extra-binding is left empty, it will be defaulted.
- planned_units: int = 1¶
Number of non-dying planned units that are expected to be running this application.
Use with caution.
- relations: Iterable[RelationBase]¶
All relations that currently exist for this charm.
- secrets: Iterable[Secret]¶
The secrets this charm has access to (as an owner, or as a grantee).
The presence of a secret in this list entails that the charm can read it. Whether it can manage it or not depends on the individual secret’s owner flag.
- storages: Iterable[Storage]¶
All attached storage instances for this charm.
If a storage is not attached, omit it from this listing.
- stored_states: Iterable[StoredState]¶
Contents of a charm’s stored state.
- unit_status: _EntityStatus = UnknownStatus()¶
Status of the unit.
- class ops.testing.Storage(name: str, *, index: int = <factory>)[source]¶
Bases:
_MaxPositionalArgs
Represents an (attached) storage made available to the charm container.
- get_filesystem(ctx: Context) pathlib.Path [source]¶
Simulated filesystem root in this context.
- class ops.testing.StoredState(name: str = '_stored', *, owner_path: str | None = None, content: dict[str, Any] = <factory>, _data_type_name: str = 'StoredStateData')[source]¶
Bases:
_MaxPositionalArgs
Represents unit-local state that persists across events.
- content: dict[str, Any]¶
The content of the
ops.StoredState
instance.
- class ops.testing.SubordinateRelation(endpoint: str, interface: str | None = None, *, id: int = <factory>, local_app_data: RawDataBagContents = <factory>, local_unit_data: RawDataBagContents = <factory>, remote_app_data: RawDataBagContents = <factory>, remote_unit_data: RawDataBagContents = <factory>, remote_app_name: str = 'remote', remote_unit_id: int = 0)[source]¶
Bases:
RelationBase
A relation to share data between a subordinate and a principal charm.
- class ops.testing.TCPPort(port: int = None, *, protocol: _RawPortProtocolLiteral = 'tcp')[source]¶
Bases:
Port
Represents a TCP port on the charm host.
- class ops.testing.UDPPort(port: int = None, *, protocol: _RawPortProtocolLiteral = 'udp')[source]¶
Bases:
Port
Represents a UDP port on the charm host.
- class ops.testing.UnknownStatus[source]¶
Bases:
_EntityStatus
,UnknownStatus
The unit status is unknown.
A unit-agent has finished calling install, config-changed and start, but the charm has not called status-set yet.
This status is read-only; trying to set unit or application status to
UnknownStatus
will raiseInvalidStatusError
.- name: Literal['unknown'] = 'unknown'¶
- class ops.testing.WaitingStatus(message: str = '')[source]¶
Bases:
_EntityStatus
,WaitingStatus
The unit or application is waiting on a charm it’s integrated with.
Set this status when waiting on a charm this is integrated with. For example, a web app charm would set “waiting” status when it is integrated with a database charm that is not ready yet (it might be creating a database). In contrast to
MaintenanceStatus
, “waiting” reflects activity on related units, not on this unit or charm.- name: Literal['waiting'] = 'waiting'¶
- class ops.testing.errors.ContextSetupError¶
Bases:
RuntimeError
Raised by Context when setup fails.
- class ops.testing.errors.AlreadyEmittedError¶
Bases:
RuntimeError
Raised when
run()
is called more than once.
- class ops.testing.errors.ScenarioRuntimeError¶
Bases:
RuntimeError
Base class for exceptions raised by the runtime module.
- class ops.testing.errors.UncaughtCharmError¶
Bases:
ScenarioRuntimeError
Error raised if the charm raises while handling the event being dispatched.
- class ops.testing.errors.InconsistentScenarioError¶
Bases:
ScenarioRuntimeError
Error raised when the combination of state and event is inconsistent.
- class ops.testing.errors.StateValidationError¶
Bases:
RuntimeError
Raised when individual parts of the State are inconsistent.
- class ops.testing.errors.MetadataNotFoundError¶
Bases:
RuntimeError
Raised when a metadata file can’t be found in the provided charm root.
- class ops.testing.errors.ActionMissingFromContextError¶
Bases:
Exception
Raised when the user attempts to invoke action hook tools outside an action context.
- class ops.testing.errors.NoObserverError¶
Bases:
RuntimeError
Error raised when the event being dispatched has no registered observers.
- class ops.testing.errors.BadOwnerPath¶
Bases:
RuntimeError
Error raised when the owner path does not lead to a valid ObjectEvents instance.