Skip to content

Commit

Permalink
Merge branch 'main' into extra-args-handlers-1129
Browse files Browse the repository at this point in the history
  • Loading branch information
tonyandrewmeyer authored Mar 12, 2024
2 parents dc54d5d + bc0bf8f commit c646788
Show file tree
Hide file tree
Showing 6 changed files with 53 additions and 20 deletions.
17 changes: 9 additions & 8 deletions ops/charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -507,9 +507,10 @@ def restore(self, snapshot: Dict[str, Any]):
class RelationCreatedEvent(RelationEvent):
"""Event triggered when a new relation is created.
This is triggered when a new relation to another app is added in Juju. This
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 should be established before start.
relations will trigger `RelationCreatedEvent` before :class:`StartEvent` is
emitted.
"""
unit: None # pyright: ignore[reportIncompatibleVariableOverride]
"""Always ``None``."""
Expand All @@ -521,7 +522,7 @@ class RelationJoinedEvent(RelationEvent):
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 settings that can be
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.
Expand All @@ -539,13 +540,13 @@ class RelationChangedEvent(RelationEvent):
the callback method bound to this event.
This event always fires once, after :class:`RelationJoinedEvent`, and
will subsequently fire whenever that remote unit changes its settings for
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 settings. They should not error if the settings
are incomplete, since it can be guaranteed that when the remote unit or
application changes its settings, the event will fire again.
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 settings that may be queried, or set, are determined by the relation's
The data that may be queried, or set, are determined by the relation's
interface.
"""

Expand Down
7 changes: 7 additions & 0 deletions ops/framework.py
Original file line number Diff line number Diff line change
Expand Up @@ -954,6 +954,13 @@ def _reemit(self, single_event_path: Optional[str] = None):
# Regular call to the registered method.
custom_handler(event)

else:
logger.warning(
f"Reference to ops.Object at path {observer_path} has been garbage collected "
"between when the charm was initialised and when the event was emitted. "
"Make sure sure you store a reference to the observer."
)

if event.deferred:
deferred = True
else:
Expand Down
16 changes: 8 additions & 8 deletions ops/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ def app(self) -> 'Application':
def relations(self) -> 'RelationMapping':
"""Mapping of endpoint to list of :class:`Relation`.
Answers the question "what am I currently related to".
Answers the question "what am I currently integrated with".
See also :meth:`.get_relation`.
In a ``relation-broken`` event, the broken relation is excluded from
Expand Down Expand Up @@ -236,7 +236,7 @@ def get_relation(
given application has more than one relation on a given endpoint.
Raises:
TooManyRelatedAppsError: is raised if there is more than one relation to the
TooManyRelatedAppsError: is raised if there is more than one integration with the
supplied relation_name and no relation_id was supplied
"""
return self.relations._get_unique(relation_name, relation_id)
Expand Down Expand Up @@ -315,8 +315,8 @@ def get(self, entity_type: 'UnitOrApplicationType', name: str):
class Application:
"""Represents a named application in the model.
This might be this charm's application, or might be an application this charm is related
to. Charmers should not instantiate Application objects directly, but should use
This might be this charm's application, or might be an application this charm is integrated
with. Charmers should not instantiate Application objects directly, but should use
:attr:`Model.app` to get the application this unit is part of, or
:meth:`Model.get_app` if they need a reference to a given application.
"""
Expand Down Expand Up @@ -472,7 +472,7 @@ class Unit:
"""Represents a named unit in the model.
This might be the current unit, another unit of the charm's application, or a unit of
another application that the charm is related to.
another application that the charm is integrated with.
"""

name: str
Expand Down Expand Up @@ -1865,8 +1865,8 @@ class MaintenanceStatus(StatusBase):
class WaitingStatus(StatusBase):
"""A unit is unable to progress.
The unit is unable to progress to an active state because an application to which
it is related is not running.
The unit is unable to progress to an active state because an application with which
it is integrated is not running.
"""
name = 'waiting'
Expand Down Expand Up @@ -2856,7 +2856,7 @@ class ModelError(Exception):


class TooManyRelatedAppsError(ModelError):
"""Raised by :meth:`Model.get_relation` if there is more than one related application."""
"""Raised by :meth:`Model.get_relation` if there is more than one integrated application."""

def __init__(self, relation_name: str, num_related: int, max_supported: int):
super().__init__('Too many remote applications on {} ({} > {})'.format(
Expand Down
6 changes: 3 additions & 3 deletions ops/testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -819,8 +819,8 @@ def add_relation(self, relation_name: str, remote_app: str, *,
})
Args:
relation_name: The relation on the charm that is being related to.
remote_app: The name of the application that is being related to.
relation_name: The relation on the charm that is being integrated with.
remote_app: The name of the application that is being integrated with.
To add a peer relation, set to the name of *this* application.
app_data: If provided, also add a new unit to the relation
(triggering relation-joined) and set the *application* relation data
Expand Down Expand Up @@ -1339,7 +1339,7 @@ def set_leader(self, is_leader: bool = True) -> None:
If this charm becomes a leader then `leader_elected` will be triggered. If :meth:`begin`
has already been called, then the charm's peer relation should usually be added *prior* to
calling this method (with :meth:`add_relation`) to properly initialize and make
calling this method (with :meth:`add_relation`) to properly initialise and make
available relation data that leader elected hooks may want to access.
Args:
Expand Down
3 changes: 2 additions & 1 deletion test/charms/test_main/src/charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@
import sys
import typing

import ops

sys.path.append('lib')

import ops

logger = logging.getLogger()

Expand Down
24 changes: 24 additions & 0 deletions test/test_charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,30 @@ def _on_start(self, event: ops.EventBase):
# check that the event has been seen by the observer
self.assertIsInstance(charm.seen, ops.StartEvent)

def test_observer_not_referenced_warning(self):
class MyObj(ops.Object):
def __init__(self, charm: ops.CharmBase):
super().__init__(charm, "obj")
framework.observe(charm.on.start, self._on_start)

def _on_start(self, _: ops.StartEvent):
raise RuntimeError() # never reached!

class MyCharm(ops.CharmBase):
def __init__(self, *args: typing.Any):
super().__init__(*args)
MyObj(self) # not assigned!
framework.observe(self.on.start, self._on_start)

def _on_start(self, _: ops.StartEvent):
pass # is reached

framework = self.create_framework()
c = MyCharm(framework)
with self.assertLogs() as logs:
c.on.start.emit()
assert any('Reference to ops.Object' in log for log in logs.output)

def test_empty_action(self):
meta = ops.CharmMeta.from_yaml('name: my-charm', '')
self.assertEqual(meta.actions, {})
Expand Down

0 comments on commit c646788

Please sign in to comment.