models. In order to do anything useful with a model, the juju lib must
connect to one of these endpoints. There are several ways to do this.
+For api docs, see :class:`juju.model.Model`.
+
Connecting to the Current Model
-------------------------------
await model.disconnect()
await controller.destroy_model(model.info.uuid)
model = None
+
+
+Adding Machines and Containers
+------------------------------
+To add a machine or container, connect to a model and then call its
+:meth:`~juju.model.Model.add_machine` method. A
+:class:`~juju.machine.Machine` instance is returned. The machine id
+can be used to deploy a charm to a specific machine or container.
+
+.. code:: python
+
+ from juju.model import Model
+
+ MB = 1
+ GB = 1024
+
+
+ model = Model()
+ await model.connect_current()
+
+ # add a new default machine
+ machine1 = await model.add_machine()
+
+ # add a machine with constraints, disks, and series specified
+ machine2 = await model.add_machine(
+ constraints={
+ 'mem': 256 * MB,
+ },
+ disks=[{
+ 'pool': 'rootfs',
+ 'size': 10 * GB,
+ 'count': 1,
+ }],
+ series='xenial',
+ )
+
+ # add a lxd container to machine2
+ machine3 = await model.add_machine(
+ 'lxd:{}'.format(machine2.id))
+
+ # deploy charm to the lxd container
+ application = await model.deploy(
+ 'ubuntu-10',
+ application_name='ubuntu',
+ series='xenial',
+ channel='stable',
+ to=machine3.id
+ )
+
+ # remove application
+ await application.remove()
+
+ # destroy machines - note that machine3 must be destroyed before machine2
+ # since it's a container on machine2
+ await machine3.destroy(force=True)
+ await machine2.destroy(force=True)
+ await machine1.destroy(force=True)
+
+
+Reacting to Changes in a Model
+------------------------------
+To watch for and respond to changes in a model, register an observer with the
+model. The easiest way to do this is by creating a
+:class:`juju.model.ModelObserver` subclass.
+
+.. code:: python
+
+ from juju.model import Model, ModelObserver
+
+ class MyModelObserver(ModelObserver):
+ async def on_change(self, delta, old, new, model):
+ # The raw change data (dict) from the websocket.
+ print(delta.data)
+
+ # The entity type (str) affected by this change.
+ # One of ('action', 'application', 'annotation', 'machine',
+ # 'unit', 'relation')
+ print(delta.entity)
+
+ # The type (str) of change.
+ # One of ('add', 'change', 'remove')
+ print(delta.type)
+
+ # The 'old' and 'new' parameters are juju.model.ModelEntity
+ # instances which represent an entity in the model both before
+ # this change was applied (old) and after (new).
+
+ # If an entity is being added to the model, the 'old' param
+ # will be None.
+ if delta.type == 'add':
+ assert(old is None)
+
+ # If an entity is being removed from the model, the 'new' param
+ # will be None.
+ if delta.type == 'remove':
+ assert(new is None)
+
+ # The 'old' and 'new' parameters, when not None, will be instances
+ # of a juju.model.ModelEntity subclass. The type of the subclass
+ # depends on the value of 'delta.entity', for example:
+ #
+ # delta.entity type
+ # ------------ ----
+ # 'action' -> juju.action.Action
+ # 'application' -> juju.application.Application
+ # 'annotation' -> juju.annotation.Annotation
+ # 'machine' -> juju.machine.Machine
+ # 'unit' -> juju.unit.Unit
+ # 'relation' -> juju.relation.Relation
+
+ # Finally, the 'model' parameter is a reference to the
+ # juju.model.Model instance to which this observer is attached.
+ print(id(model))
+
+
+ model = Model()
+ await model.connect_current()
+
+ model.add_observer(MyModelObserver())
+
+
+Every change in the model will result in a call to the `on_change()`
+method of your observer(s).
+
+To target your code more precisely, define method names that correspond
+to the entity and type of change that you wish to handle.
+
+.. code:: python
+
+ from juju.model import Model, ModelObserver
+
+ class MyModelObserver(ModelObserver):
+ async def on_application_change(self, delta, old, new, model):
+ # Both 'old' and 'new' params will be instances of
+ # juju.application.Application
+ pass
+
+ async def on_unit_remove(self, delta, old, new, model):
+ # Since a unit is being removed, the 'new' param will always
+ # be None in this handler. The 'old' param will be an instance
+ # of juju.unit.Unit - the state of the unit before it was removed.
+ pass
+
+ async def on_machine_add(self, delta, old, new, model):
+ # Since a machine is being added, the 'old' param will always be
+ # None in this handler. The 'new' param will be an instance of
+ # juju.machine.Machine.
+ pass
+
+ async def on_change(self, delta, old, new, model):
+ # The catch-all handler - will be called whenever a more
+ # specific handler method is not defined.
+
+
+Any :class:`juju.model.ModelEntity` object can be observed directly by
+registering callbacks on the object itself.
+
+.. code:: python
+
+ import logging
+
+ async def on_app_change(delta, old, new, model):
+ logging.debug('App changed: %r', new)
+
+ async def on_app_remove(delta, old, new, model):
+ logging.debug('App removed: %r', old)
+
+ ubuntu_app = await model.deploy(
+ 'ubuntu',
+ application_name='ubuntu',
+ series='trusty',
+ channel='stable',
+ )
+ ubuntu_app.on_change(on_app_change)
+ ubuntu_app.on_remove(on_app_remove)