From 9a4bab36ea586f4baba509e1e2a8842d51ebe32d Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Fri, 12 Dec 2025 21:58:47 +1300 Subject: [PATCH 01/17] test: add type annotations for test_network --- pyproject.toml | 16 +++++- testing/tests/test_e2e/test_network.py | 71 ++++++++++++++++---------- 2 files changed, 59 insertions(+), 28 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index ed1ee4ede..980d7e22d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -269,8 +269,20 @@ convention = "google" builtins-ignorelist = ["id", "min", "map", "range", "type", "TimeoutError", "ConnectionError", "Warning", "input", "format"] [tool.pyright] -include = ["ops/*.py", "ops/_private/*.py", "test/*.py", "test/charms/*/src/*.py", "testing/src/*.py"] -exclude = ["tracing/*"] +include = ["ops/*.py", "ops/_private/*.py", "test/*.py", "test/charms/*/src/*.py", "testing/src/*.py", "testing/tests/*.py", "testing/tests/test_e2e/*.py"] +exclude = [ + "tracing/*", + "testing/tests/test_e2e/test_secrets.py", + "testing/tests/test_e2e/test_relations.py", + "testing/tests/test_e2e/test_trace_data.py", + "testing/tests/test_e2e/test_juju_log.py", + "testing/tests/test_e2e/test_status.py", + "testing/tests/test_e2e/test_storage.py", + "testing/tests/test_e2e/test_manager.py", + "testing/tests/test_e2e/test_ports.py", + "testing/tests/test_e2e/test_rubbish_events.py", + "testing/tests/test_e2e/test_pebble.py", +] extraPaths = ["testing", "tracing"] pythonVersion = "3.10" # check no python > 3.10 features are used pythonPlatform = "All" diff --git a/testing/tests/test_e2e/test_network.py b/testing/tests/test_e2e/test_network.py index 412a21245..bfb552d08 100644 --- a/testing/tests/test_e2e/test_network.py +++ b/testing/tests/test_e2e/test_network.py @@ -1,9 +1,7 @@ from __future__ import annotations +import ops import pytest -from ops import RelationNotFoundError -from ops.charm import CharmBase -from ops.framework import Framework from scenario import Context from scenario.state import ( @@ -17,18 +15,18 @@ @pytest.fixture(scope='function') -def mycharm(): - class MyCharm(CharmBase): +def mycharm() -> type[ops.CharmBase]: + class MyCharm(ops.CharmBase): _call = None called = False - def __init__(self, framework: Framework): + def __init__(self, framework: ops.Framework): super().__init__(framework) for evt in self.on.events().values(): self.framework.observe(evt, self._on_event) - def _on_event(self, event): + def _on_event(self, event: ops.EventBase) -> None: if MyCharm._call: MyCharm.called = True MyCharm._call(self, event) @@ -36,8 +34,8 @@ def _on_event(self, event): return MyCharm -def test_ip_get(mycharm): - ctx = Context( +def test_ip_get(mycharm: type[ops.CharmBase]) -> None: + ctx: Context[ops.CharmBase] = Context( mycharm, meta={ 'name': 'foo', @@ -65,17 +63,30 @@ def test_ip_get(mycharm): ) as mgr: # we have a network for the relation rel = mgr.charm.model.get_relation('metrics-endpoint') - assert str(mgr.charm.model.get_binding(rel).network.bind_address) == '192.0.2.0' + assert rel is not None + binding = mgr.charm.model.get_binding(rel) + assert binding is not None + network = binding.network + assert network is not None + assert str(network.bind_address) == '192.0.2.0' # we have a network for a binding without relations on it - assert str(mgr.charm.model.get_binding('deadnodead').network.bind_address) == '192.0.2.0' + binding = mgr.charm.model.get_binding('deadnodead') + assert binding is not None + network = binding.network + assert network is not None + assert str(network.bind_address) == '192.0.2.0' # and an extra binding - assert str(mgr.charm.model.get_binding('foo').network.bind_address) == '4.4.4.4' + binding = mgr.charm.model.get_binding('foo') + assert binding is not None + network = binding.network + assert network is not None + assert str(network.bind_address) == '4.4.4.4' -def test_no_sub_binding(mycharm): - ctx = Context( +def test_no_sub_binding(mycharm: type[ops.CharmBase]) -> None: + ctx: Context[ops.CharmBase] = Context( mycharm, meta={ 'name': 'foo', @@ -91,15 +102,15 @@ def test_no_sub_binding(mycharm): ] ), ) as mgr: - with pytest.raises(RelationNotFoundError): + with pytest.raises(ops.RelationNotFoundError): # sub relations have no network - mgr.charm.model.get_binding('bar').network + mgr.charm.model.get_binding('bar').network # type: ignore[union-attr] -def test_no_relation_error(mycharm): +def test_no_relation_error(mycharm: type[ops.CharmBase]) -> None: """Attempting to call get_binding on a non-existing relation -> RelationNotFoundError""" - ctx = Context( + ctx: Context[ops.CharmBase] = Context( mycharm, meta={ 'name': 'foo', @@ -122,12 +133,12 @@ def test_no_relation_error(mycharm): networks={Network('bar')}, ), ) as mgr: - with pytest.raises(RelationNotFoundError): - mgr.charm.model.get_binding('foo').network + with pytest.raises(ops.RelationNotFoundError): + mgr.charm.model.get_binding('foo').network # type: ignore[union-attr] -def test_juju_info_network_default(mycharm): - ctx = Context( +def test_juju_info_network_default(mycharm: type[ops.CharmBase]) -> None: + ctx: Context[ops.CharmBase] = Context( mycharm, meta={'name': 'foo'}, ) @@ -137,11 +148,15 @@ def test_juju_info_network_default(mycharm): State(), ) as mgr: # we have a network for the relation - assert str(mgr.charm.model.get_binding('juju-info').network.bind_address) == '192.0.2.0' + binding = mgr.charm.model.get_binding('juju-info') + assert binding is not None + network = binding.network + assert network is not None + assert str(network.bind_address) == '192.0.2.0' -def test_explicit_juju_info_network_override(mycharm): - ctx = Context( +def test_explicit_juju_info_network_override(mycharm: type[ops.CharmBase]) -> None: + ctx: Context[ops.CharmBase] = Context( mycharm, meta={ 'name': 'foo', @@ -154,4 +169,8 @@ def test_explicit_juju_info_network_override(mycharm): ctx.on.update_status(), State(), ) as mgr: - assert mgr.charm.model.get_binding('juju-info').network.bind_address + binding = mgr.charm.model.get_binding('juju-info') + assert binding is not None + network = binding.network + assert network is not None + assert network.bind_address From 0f3eb00f51eb8f944ce83ec188dc0fb085ae9c00 Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Fri, 12 Dec 2025 22:04:21 +1300 Subject: [PATCH 02/17] test: add type annotations to test_cloud_spec. --- testing/tests/test_e2e/test_cloud_spec.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/testing/tests/test_e2e/test_cloud_spec.py b/testing/tests/test_e2e/test_cloud_spec.py index 33eb4b11a..97583aa94 100644 --- a/testing/tests/test_e2e/test_cloud_spec.py +++ b/testing/tests/test_e2e/test_cloud_spec.py @@ -12,11 +12,11 @@ def __init__(self, framework: ops.Framework): for evt in self.on.events().values(): self.framework.observe(evt, self._on_event) - def _on_event(self, event): + def _on_event(self, event: ops.EventBase) -> None: pass -def test_get_cloud_spec(): +def test_get_cloud_spec() -> None: scenario_cloud_spec = scenario.CloudSpec( type='lxd', name='localhost', @@ -51,7 +51,7 @@ def test_get_cloud_spec(): assert mgr.charm.model.get_cloud_spec() == expected_cloud_spec -def test_get_cloud_spec_error(): +def test_get_cloud_spec_error() -> None: ctx = scenario.Context(MyCharm, meta={'name': 'foo'}) state = scenario.State(model=scenario.Model(name='lxd-model', type='lxd')) with ctx(ctx.on.start(), state) as mgr: @@ -59,7 +59,7 @@ def test_get_cloud_spec_error(): mgr.charm.model.get_cloud_spec() -def test_get_cloud_spec_untrusted(): +def test_get_cloud_spec_untrusted() -> None: cloud_spec = scenario.CloudSpec(type='lxd', name='localhost') ctx = scenario.Context(MyCharm, meta={'name': 'foo'}) state = scenario.State( From 14b2923405f7bc0c67d06f10d09a80d494367314 Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Fri, 12 Dec 2025 22:12:07 +1300 Subject: [PATCH 03/17] test: add type annotations to test_play_assertions. --- .../tests/test_e2e/test_play_assertions.py | 46 +++++++++---------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/testing/tests/test_e2e/test_play_assertions.py b/testing/tests/test_e2e/test_play_assertions.py index d804ac962..69d20e1fb 100644 --- a/testing/tests/test_e2e/test_play_assertions.py +++ b/testing/tests/test_e2e/test_play_assertions.py @@ -2,29 +2,27 @@ import dataclasses +import ops import pytest -from ops.charm import CharmBase -from ops.framework import Framework -from ops.model import ActiveStatus, BlockedStatus from scenario.state import Relation, State -from scenario.state import BlockedStatus as ScenarioBlockedStatus +from scenario.state import BlockedStatus from ..helpers import jsonpatch_delta, trigger @pytest.fixture(scope='function') -def mycharm(): - class MyCharm(CharmBase): +def mycharm() -> type[ops.CharmBase]: + class MyCharm(ops.CharmBase): _call = None called = False - def __init__(self, framework: Framework): + def __init__(self, framework: ops.Framework): super().__init__(framework) for evt in self.on.events().values(): self.framework.observe(evt, self._on_event) - def _on_event(self, event): + def _on_event(self, event: ops.EventBase) -> None: if MyCharm._call: MyCharm.called = True MyCharm._call(self, event) @@ -32,24 +30,22 @@ def _on_event(self, event): return MyCharm -def test_charm_heals_on_start(mycharm): - def pre_event(charm): - assert charm.unit.status == BlockedStatus('foo') - assert not charm.called +def test_charm_heals_on_start(mycharm: type[ops.CharmBase]) -> None: + def pre_event(charm: ops.CharmBase) -> None: + assert charm.unit.status == ops.BlockedStatus('foo') + assert not charm.called # type: ignore[attr-defined] - def call(charm, _): + def call(charm: ops.CharmBase, _: ops.EventBase) -> None: if charm.unit.status.message == 'foo': - charm.unit.status = ActiveStatus('yabadoodle') + charm.unit.status = ops.ActiveStatus('yabadoodle') - def post_event(charm): - assert charm.unit.status == ActiveStatus('yabadoodle') - assert charm.called + def post_event(charm: ops.CharmBase) -> None: + assert charm.unit.status == ops.ActiveStatus('yabadoodle') + assert charm.called # type: ignore[attr-defined] - mycharm._call = call + mycharm._call = call # type: ignore[attr-defined] - initial_state = State( - config={'foo': 'bar'}, leader=True, unit_status=ScenarioBlockedStatus('foo') - ) + initial_state = State(config={'foo': 'bar'}, leader=True, unit_status=BlockedStatus('foo')) out = trigger( initial_state, @@ -61,7 +57,7 @@ def post_event(charm): pre_event=pre_event, ) - assert out.unit_status == ActiveStatus('yabadoodle') + assert out.unit_status == ops.ActiveStatus('yabadoodle') out_purged = dataclasses.replace(out, stored_states=initial_state.stored_states) assert jsonpatch_delta(out_purged, initial_state) == [ @@ -78,10 +74,10 @@ def post_event(charm): ] -def test_relation_data_access(mycharm): - mycharm._call = lambda *_: True +def test_relation_data_access(mycharm: type[ops.CharmBase]) -> None: + mycharm._call = lambda *_: True # type: ignore[misc] - def check_relation_data(charm): + def check_relation_data(charm: ops.CharmBase) -> None: foo_relations = charm.model.relations['relation_test'] assert len(foo_relations) == 1 foo_rel = foo_relations[0] From 34adbbdcd080c5a51afc6a6e6eeec2d341fabc68 Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Fri, 12 Dec 2025 22:18:09 +1300 Subject: [PATCH 04/17] test: add type annotations to test_vroot. --- testing/tests/test_e2e/test_vroot.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/testing/tests/test_e2e/test_vroot.py b/testing/tests/test_e2e/test_vroot.py index dd299f8e8..8deb45510 100644 --- a/testing/tests/test_e2e/test_vroot.py +++ b/testing/tests/test_e2e/test_vroot.py @@ -1,6 +1,7 @@ from __future__ import annotations import tempfile +from collections.abc import Generator from pathlib import Path import pytest @@ -25,7 +26,7 @@ def __init__(self, framework: Framework): @pytest.fixture -def charm_virtual_root(): +def charm_virtual_root() -> Generator[Path]: with tempfile.TemporaryDirectory() as mycharm_virtual_root: t = Path(mycharm_virtual_root) src = t / 'src' @@ -41,7 +42,7 @@ def charm_virtual_root(): yield t -def test_charm_virtual_root(charm_virtual_root): +def test_charm_virtual_root(charm_virtual_root: Path) -> None: out = trigger( State(), 'start', @@ -52,7 +53,7 @@ def test_charm_virtual_root(charm_virtual_root): assert out.unit_status == ActiveStatus('hello world') -def test_charm_virtual_root_cleanup_if_exists(charm_virtual_root): +def test_charm_virtual_root_cleanup_if_exists(charm_virtual_root: Path) -> None: meta_file = charm_virtual_root / 'metadata.yaml' raw_ori_meta = yaml.safe_dump({'name': 'karl'}) meta_file.write_text(raw_ori_meta) @@ -73,7 +74,7 @@ def test_charm_virtual_root_cleanup_if_exists(charm_virtual_root): assert meta_file.exists() -def test_charm_virtual_root_cleanup_if_not_exists(charm_virtual_root): +def test_charm_virtual_root_cleanup_if_not_exists(charm_virtual_root: Path) -> None: meta_file = charm_virtual_root / 'metadata.yaml' assert not meta_file.exists() From 8350e8d04a566b087c611d745b1e40202543bb2f Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Fri, 12 Dec 2025 22:19:30 +1300 Subject: [PATCH 05/17] test: add type annotations for test_resource. --- testing/tests/test_e2e/test_resource.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/tests/test_e2e/test_resource.py b/testing/tests/test_e2e/test_resource.py index 2aeac5408..e89677b8d 100644 --- a/testing/tests/test_e2e/test_resource.py +++ b/testing/tests/test_e2e/test_resource.py @@ -12,7 +12,7 @@ class ResourceCharm(ops.CharmBase): - def __init__(self, framework): + def __init__(self, framework: ops.Framework): super().__init__(framework) From 0a8c1d561779c993ba8381452879ef5618dcac82 Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Sat, 13 Dec 2025 00:29:56 +1300 Subject: [PATCH 06/17] test: add type annotations to test_state. --- testing/tests/test_e2e/test_state.py | 151 +++++++++++++++------------ 1 file changed, 82 insertions(+), 69 deletions(-) diff --git a/testing/tests/test_e2e/test_state.py b/testing/tests/test_e2e/test_state.py index b8ffa1523..14047c71b 100644 --- a/testing/tests/test_e2e/test_state.py +++ b/testing/tests/test_e2e/test_state.py @@ -16,7 +16,6 @@ from scenario.state import ( _DEFAULT_JUJU_DATABAG, - _Event, _next_storage_index, Address, BindAddress, @@ -57,7 +56,7 @@ @pytest.fixture(scope='function') -def mycharm(): +def mycharm() -> type[CharmBase]: class MyCharmEvents(CharmEvents): @classmethod def define_event(cls, event_kind: str, event_type: 'type[EventBase]'): @@ -66,16 +65,16 @@ def define_event(cls, event_kind: str, event_type: 'type[EventBase]'): return super().define_event(event_kind, event_type) class MyCharm(CharmBase): - _call: Callable[[MyCharm, _Event], None] | None = None + _call: Callable[[EventBase], None] | None = None called = False - on = MyCharmEvents() + on = MyCharmEvents() # type: ignore[assignment] def __init__(self, framework: Framework): super().__init__(framework) for evt in self.on.events().values(): self.framework.observe(evt, self._on_event) - def _on_event(self, event): + def _on_event(self, event: EventBase) -> None: if self._call: MyCharm.called = True self._call(event) @@ -84,11 +83,11 @@ def _on_event(self, event): @pytest.fixture -def state(): +def state() -> State: return State(config={'foo': 'bar'}, leader=True) -def test_bare_event(state, mycharm): +def test_bare_event(state: State, mycharm: type[CharmBase]) -> None: out = trigger( state, 'start', @@ -100,8 +99,8 @@ def test_bare_event(state, mycharm): assert jsonpatch_delta(state, out_purged) == [] -def test_leader_get(state, mycharm): - def pre_event(charm): +def test_leader_get(state: State, mycharm: type[CharmBase]) -> None: + def pre_event(charm: CharmBase) -> None: assert charm.unit.is_leader() trigger( @@ -114,8 +113,8 @@ def pre_event(charm): ) -def test_status_setting(state, mycharm): - def call(charm: CharmBase, e): +def test_status_setting(state: State, mycharm: type[CharmBase]) -> None: + def call(charm: CharmBase, e: EventBase) -> None: if isinstance(e, CollectStatusEvent): return @@ -123,7 +122,7 @@ def call(charm: CharmBase, e): charm.unit.status = ActiveStatus('foo test') charm.app.status = WaitingStatus('foo barz') - mycharm._call = call + mycharm._call = call # type: ignore[attr-defined] out = trigger( state, 'start', @@ -146,8 +145,8 @@ def call(charm: CharmBase, e): @pytest.mark.parametrize('connect', (True, False)) -def test_container(connect, mycharm): - def pre_event(charm: CharmBase): +def test_container(connect: bool, mycharm: type[CharmBase]) -> None: + def pre_event(charm: CharmBase) -> None: container = charm.unit.get_container('foo') assert container is not None assert container.name == 'foo' @@ -165,8 +164,8 @@ def pre_event(charm: CharmBase): ) -def test_relation_get(mycharm): - def pre_event(charm: CharmBase): +def test_relation_get(mycharm: type[CharmBase]) -> None: + def pre_event(charm: CharmBase) -> None: rel = charm.model.get_relation('foo') assert rel is not None assert rel.data[charm.app]['a'] == 'because' @@ -207,8 +206,8 @@ def pre_event(charm: CharmBase): ) -def test_relation_set(mycharm): - def event_handler(charm: CharmBase, _): +def test_relation_set(mycharm: type[CharmBase]) -> None: + def event_handler(charm: CharmBase, _: EventBase) -> None: rel = charm.model.get_relation('foo') assert rel is not None rel.data[charm.app]['a'] = 'b' @@ -223,7 +222,7 @@ def event_handler(charm: CharmBase, _): assert charm.unit.is_leader() - def pre_event(charm: CharmBase): + def pre_event(charm: CharmBase) -> None: assert charm.model.get_relation('foo') assert charm.model.app.planned_units() == 4 @@ -234,7 +233,7 @@ def pre_event(charm: CharmBase): # with pytest.raises(Exception): # rel.data[charm.model.get_unit("remote/1")]["c"] = "d" - mycharm._call = event_handler + mycharm._call = event_handler # type: ignore[attr-defined] relation = Relation( endpoint='foo', interface='bar', @@ -247,7 +246,7 @@ def pre_event(charm: CharmBase): relations={relation}, ) - assert not mycharm.called + assert not mycharm.called # type: ignore[attr-defined] out = trigger( state, event='start', @@ -258,7 +257,7 @@ def pre_event(charm: CharmBase): }, pre_event=pre_event, ) - assert mycharm.called + assert mycharm.called # type: ignore[attr-defined] assert asdict(out.get_relation(relation.id)) == asdict( replace( @@ -274,7 +273,7 @@ def pre_event(charm: CharmBase): } -def test_checkinfo_changeid_none(): +def test_checkinfo_changeid_none() -> None: info = CheckInfo('foo', change_id=None) assert info.change_id, 'None should result in a random change_id' info2 = CheckInfo('foo') # None is also the default. @@ -282,7 +281,7 @@ def test_checkinfo_changeid_none(): @pytest.mark.parametrize('id', ('', '28')) -def test_checkinfo_changeid(id: str | None): +def test_checkinfo_changeid(id: str | None) -> None: info = CheckInfo('foo', change_id=ops.pebble.ChangeID(id)) assert info.change_id == ops.pebble.ChangeID(id) @@ -297,24 +296,24 @@ def test_checkinfo_changeid(id: str | None): (Network, (0, 3)), ], ) -def test_positional_arguments(klass, num_args): +def test_positional_arguments(klass: type[Any], num_args: tuple[int, ...]) -> None: for num in num_args: args = (None,) * num with pytest.raises(TypeError): klass(*args) -def test_model_positional_arguments(): +def test_model_positional_arguments() -> None: with pytest.raises(TypeError): - Model('', '') + Model('', '') # type: ignore[misc] -def test_container_positional_arguments(): +def test_container_positional_arguments() -> None: with pytest.raises(TypeError): - Container('', True) + Container('', True) # type: ignore[misc] -def test_container_default_values(): +def test_container_default_values() -> None: name = 'foo' container = Container(name) assert container.name == name @@ -327,7 +326,7 @@ def test_container_default_values(): assert container._base_plan == {} -def test_state_default_values(): +def test_state_default_values() -> None: state = State() assert state.config == {} assert state.relations == frozenset() @@ -346,7 +345,7 @@ def test_state_default_values(): assert state.workload_version == '' -def test_deepcopy_state(): +def test_deepcopy_state() -> None: containers = [Container('foo'), Container('bar')] state = State(containers=containers) state_copy = copy.deepcopy(state) @@ -355,7 +354,7 @@ def test_deepcopy_state(): assert container.name == copied_container.name -def test_replace_state(): +def test_replace_state() -> None: containers = [Container('foo'), Container('bar')] state = State(containers=containers, leader=True) state2 = replace(state, leader=False) @@ -389,7 +388,7 @@ def test_replace_state(): ) def test_immutable_content_dict( component: type[object], attribute: str, required_args: dict[str, Any] -): +) -> None: content = {'foo': 'bar'} obj1 = component(**required_args, **{attribute: content}) obj2 = component(**required_args, **{attribute: content}) @@ -417,7 +416,7 @@ def test_immutable_content_dict( ) def test_immutable_content_list( component: type[object], attribute: str, required_args: dict[str, Any] -): +) -> None: content = ['foo', 'bar'] obj1 = component(**required_args, **{attribute: content}) obj2 = component(**required_args, **{attribute: content}) @@ -441,7 +440,7 @@ def test_immutable_content_list( ) def test_immutable_content_dict_of_dicts( component: type[object], attribute: str, required_args: dict[str, Any] -): +) -> None: content = {0: {'foo': 'bar'}, 1: {'baz': 'qux'}} obj1 = component(**required_args, **{attribute: content}) obj2 = component(**required_args, **{attribute: content}) @@ -476,8 +475,11 @@ def test_immutable_content_dict_of_dicts( (StoredState(), 'stored_states', 'get_stored_state', 'name'), ], ) -def test_state_immutable(obj_in, attribute: str, get_method: str, key_attr: str, mycharm): - state_in = State(**{attribute: obj_in if isinstance(obj_in, dict) else [obj_in]}) +def test_state_immutable( + obj_in: Any, attribute: str, get_method: str, key_attr: str, mycharm: type[CharmBase] +) -> None: + kwargs: dict[str, Any] = {attribute: obj_in if isinstance(obj_in, dict) else [obj_in]} + state_in = State(**kwargs) state_out: State = trigger( state_in, @@ -508,11 +510,14 @@ def test_state_immutable(obj_in, attribute: str, get_method: str, key_attr: str, elif attribute == 'secrets': # State.get_secret only takes keyword arguments, while the others take # only positional arguments. + assert isinstance(obj_in, Secret) obj_out = state_out.get_secret(id=obj_in.id) elif attribute == 'resources': # Charms can't change resources, so there's no State.get_resource. obj_out = [r for r in state_out.resources if r == obj_in][0] else: + # This handles: Relation, PeerRelation, SubordinateRelation, Network, Container, Storage, StoredState + assert isinstance(obj_in, (RelationBase, Network, Container, Storage, StoredState)) obj_out = getattr(state_out, get_method)(getattr(obj_in, key_attr)) assert obj_in is not obj_out @@ -525,14 +530,16 @@ def test_state_immutable(obj_in, attribute: str, get_method: str, key_attr: str, SubordinateRelation, ], ) -def test_state_immutable_with_changed_data_relation(relation_type: type[RelationBase], mycharm): - def event_handler(charm: CharmBase, _): +def test_state_immutable_with_changed_data_relation( + relation_type: type[RelationBase], mycharm: type[CharmBase] +) -> None: + def event_handler(charm: CharmBase, _: EventBase) -> None: rel = charm.model.get_relation(relation_type.__name__) assert rel is not None rel.data[charm.app]['a'] = 'b' rel.data[charm.unit]['c'] = 'd' - mycharm._call = event_handler + mycharm._call = event_handler # type: ignore[attr-defined] relation_in = relation_type(relation_type.__name__) @@ -558,7 +565,7 @@ def event_handler(charm: CharmBase, _): assert relation_out.local_unit_data == {'c': 'd', **_DEFAULT_JUJU_DATABAG} -def test_state_immutable_with_changed_data_container(mycharm): +def test_state_immutable_with_changed_data_container(mycharm: type[CharmBase]) -> None: layer_name = 'my-layer' layer = ops.pebble.Layer({ 'services': { @@ -569,11 +576,11 @@ def test_state_immutable_with_changed_data_container(mycharm): } }) - def event_handler(charm: CharmBase, _): + def event_handler(charm: CharmBase, _: EventBase) -> None: container = charm.model.unit.get_container('foo') container.add_layer(layer_name, layer, combine=True) - mycharm._call = event_handler + mycharm._call = event_handler # type: ignore[attr-defined] container_in = Container('foo', can_connect=True) state_in = State(containers={container_in}) @@ -593,11 +600,11 @@ def event_handler(charm: CharmBase, _): assert container_out.layers == {layer_name: layer} -def test_state_immutable_with_changed_data_ports(mycharm): - def event_handler(charm: CharmBase, _): +def test_state_immutable_with_changed_data_ports(mycharm: type[CharmBase]) -> None: + def event_handler(charm: CharmBase, _: EventBase) -> None: charm.model.unit.open_port(protocol='tcp', port=80) - mycharm._call = event_handler + mycharm._call = event_handler # type: ignore[attr-defined] state_in = State() state_out = trigger( @@ -611,12 +618,12 @@ def event_handler(charm: CharmBase, _): assert state_out.opened_ports == {TCPPort(80)} -def test_state_immutable_with_changed_data_secret(mycharm): - def event_handler(charm: CharmBase, _): +def test_state_immutable_with_changed_data_secret(mycharm: type[CharmBase]) -> None: + def event_handler(charm: CharmBase, _: EventBase) -> None: secret = charm.model.get_secret(label='my-secret') secret.set_content({'password': 'bar'}) - mycharm._call = event_handler + mycharm._call = event_handler # type: ignore[attr-defined] secret_in = Secret({'password': 'foo'}, label='my-secret', owner='unit') state_in = State(secrets={secret_in}) @@ -633,7 +640,7 @@ def event_handler(charm: CharmBase, _): assert secret_out.latest_content == {'password': 'bar'} -def test_state_immutable_with_changed_data_stored_state(): +def test_state_immutable_with_changed_data_stored_state() -> None: class MyCharm(ops.CharmBase): _stored = ops.StoredState() @@ -751,7 +758,7 @@ def _on_start(self, event: ops.StartEvent): ), ], ) -def test_layer_from_rockcraft(rockcraft: dict[str, Any]): +def test_layer_from_rockcraft(rockcraft: dict[str, Any]) -> None: with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml') as f: yaml.safe_dump(rockcraft, f) rockcraft_path = f.name @@ -780,13 +787,15 @@ def test_layer_from_rockcraft(rockcraft: dict[str, Any]): assert layer_check_level == check.get('level', ops.pebble.CheckLevel.UNSET.value) if 'exec' in check: assert layer_check.exec is not None and 'command' in check['exec'] + assert 'command' in layer_check.exec assert layer_check.exec['command'] == check['exec']['command'] if 'tcp' in check: assert layer_check.tcp is not None and 'port' in check['tcp'] + assert 'port' in layer_check.tcp assert layer_check.tcp['port'] == check['tcp']['port'] -def test_layer_from_rockcraft_safe(): +def test_layer_from_rockcraft_safe() -> None: dangerous_yaml = """ !!python/object/apply:os.system ["echo unsafe"] """ @@ -798,8 +807,8 @@ def test_layer_from_rockcraft_safe(): layer_from_rockcraft(rockcraft_path) -def test_state_from_context(): - meta = { +def test_state_from_context() -> None: + meta: dict[str, Any] = { 'name': 'sam', 'containers': {'container': {}}, 'peers': {'peer': {'interface': 'friend'}}, @@ -836,8 +845,8 @@ class Charm(ops.CharmBase): assert state.get_stored_state('_stored', owner_path='Charm').name == '_stored' -def test_state_from_context_extend(): - meta = { +def test_state_from_context_extend() -> None: + meta: dict[str, Any] = { 'name': 'sam', 'containers': {'container': {}}, 'peers': {'peer': {'interface': 'friend'}}, @@ -878,7 +887,9 @@ class Charm(ops.CharmBase): assert state.get_relations('relreq')[0].interface == 'across' assert state.get_relations('relpro')[0].interface == 'around' assert state.get_relations('sub')[0].interface == 'below' - assert state.get_relation(relation.id).remote_app_data == {'a': 'b'} + retrieved_relation = state.get_relation(relation.id) + assert isinstance(retrieved_relation, Relation) + assert retrieved_relation.remote_app_data == {'a': 'b'} assert isinstance(state.storages, frozenset) assert len(state.storages) == 1 assert tuple(state.storages)[0].name == 'storage' @@ -888,7 +899,7 @@ class Charm(ops.CharmBase): assert state.get_stored_state('_stored', owner_path='Charm').content == {'foo': 'bar'} -def test_state_from_context_merge_config(): +def test_state_from_context_merge_config() -> None: ctx = Context( ops.CharmBase, meta={'name': 'alex'}, @@ -902,8 +913,10 @@ def test_state_from_context_merge_config(): 'rel_type,endpoint', [(Relation, 'relreq'), (PeerRelation, 'peer'), (SubordinateRelation, 'sub')], ) -def test_state_from_context_skip_exiting_relation(rel_type: type[RelationBase], endpoint: str): - meta = { +def test_state_from_context_skip_exiting_relation( + rel_type: type[RelationBase], endpoint: str +) -> None: + meta: dict[str, Any] = { 'name': 'sam', 'peers': {'peer': {'interface': 'friend'}}, 'requires': { @@ -919,8 +932,8 @@ def test_state_from_context_skip_exiting_relation(rel_type: type[RelationBase], assert state.get_relation(rel.id).local_app_data == {'a': 'b'} -def test_state_from_context_skip_exiting_container(): - meta = { +def test_state_from_context_skip_exiting_container() -> None: + meta: dict[str, Any] = { 'name': 'sam', 'containers': {'container': {}}, } @@ -934,8 +947,8 @@ def test_state_from_context_skip_exiting_container(): assert state.get_container(container.name).can_connect is False -def test_state_from_context_skip_exiting_storage(): - meta = { +def test_state_from_context_skip_exiting_storage() -> None: + meta: dict[str, Any] = { 'name': 'sam', 'storage': {'storage': {}}, } @@ -951,11 +964,11 @@ def test_state_from_context_skip_exiting_storage(): assert _next_storage_index(update=False) == next_index -def test_state_from_context_skip_exiting_stored_state(): +def test_state_from_context_skip_exiting_stored_state() -> None: class Charm(ops.CharmBase): _stored = ops.StoredState() - meta = { + meta: dict[str, Any] = { 'name': 'sam', } stored_state = StoredState(name='_stored', owner_path='Charm', content={'foo': 'bar'}) @@ -973,8 +986,8 @@ def _make_generator(items: Iterable[Any]) -> Generator[Any]: @pytest.mark.parametrize('iterable', [frozenset, tuple, list, _make_generator]) -def test_state_from_non_sets(iterable: Callable[..., Any]): - meta = { +def test_state_from_non_sets(iterable: Callable[..., Any]) -> None: + meta: dict[str, Any] = { 'name': 'sam', 'containers': {'container': {}}, 'peers': {'peer': {'interface': 'friend'}}, From 63c039a6e00dd937445cdd24fff1167e87f35b5a Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Sat, 13 Dec 2025 00:41:56 +1300 Subject: [PATCH 07/17] test: add type annotations to test_actions. --- testing/tests/test_e2e/test_actions.py | 82 +++++++++++++------------- 1 file changed, 42 insertions(+), 40 deletions(-) diff --git a/testing/tests/test_e2e/test_actions.py b/testing/tests/test_e2e/test_actions.py index 40d65fc6a..5ceec87a0 100644 --- a/testing/tests/test_e2e/test_actions.py +++ b/testing/tests/test_e2e/test_actions.py @@ -1,5 +1,7 @@ from __future__ import annotations +from typing import Any + import pytest from ops import __version__ as ops_version from ops.charm import ActionEvent, CharmBase @@ -11,7 +13,7 @@ @pytest.fixture(scope='function') -def mycharm(): +def mycharm() -> type[CharmBase]: class MyCharm(CharmBase): _evt_handler = None @@ -20,7 +22,7 @@ def __init__(self, framework: Framework): for evt in self.on.events().values(): self.framework.observe(evt, self._on_event) - def _on_event(self, event): + def _on_event(self, event: ActionEvent) -> None: if handler := self._evt_handler: handler(event) @@ -28,8 +30,8 @@ def _on_event(self, event): @pytest.mark.parametrize('baz_value', (True, False)) -def test_action_event(mycharm, baz_value): - ctx = Context( +def test_action_event(mycharm: type[CharmBase], baz_value: bool) -> None: + ctx: Context[CharmBase] = Context( mycharm, meta={'name': 'foo'}, actions={'foo': {'params': {'bar': {'type': 'number'}, 'baz': {'type': 'boolean'}}}}, @@ -43,13 +45,13 @@ def test_action_event(mycharm, baz_value): assert evt.params['baz'] is baz_value -def test_action_no_results(): +def test_action_no_results() -> None: class MyCharm(CharmBase): - def __init__(self, framework): + def __init__(self, framework: Framework): super().__init__(framework) framework.observe(self.on.act_action, self._on_act_action) - def _on_act_action(self, _): + def _on_act_action(self, _: ActionEvent) -> None: pass ctx = Context(MyCharm, meta={'name': 'foo'}, actions={'act': {}}) @@ -59,27 +61,27 @@ def _on_act_action(self, _): @pytest.mark.parametrize('res_value', ('one', 1, [2], ['bar'], (1,), {1, 2})) -def test_action_event_results_invalid(mycharm, res_value): - def handle_evt(charm: CharmBase, evt: ActionEvent): +def test_action_event_results_invalid(mycharm: type[CharmBase], res_value: object) -> None: + def handle_evt(charm: CharmBase, evt: ActionEvent) -> None: with pytest.raises((TypeError, AttributeError)): - evt.set_results(res_value) + evt.set_results(res_value) # type: ignore[arg-type] - mycharm._evt_handler = handle_evt + mycharm._evt_handler = handle_evt # type: ignore[attr-defined] ctx = Context(mycharm, meta={'name': 'foo'}, actions={'foo': {}}) ctx.run(ctx.on.action('foo'), State()) @pytest.mark.parametrize('res_value', ({'a': {'b': {'c'}}}, {'d': 'e'})) -def test_action_event_results_valid(mycharm, res_value): - def handle_evt(_: CharmBase, evt): +def test_action_event_results_valid(mycharm: type[CharmBase], res_value: dict[str, Any]) -> None: + def handle_evt(_: CharmBase, evt: ActionEvent) -> None: if not isinstance(evt, ActionEvent): return evt.set_results(res_value) evt.log('foo') evt.log('bar') - mycharm._evt_handler = handle_evt + mycharm._evt_handler = handle_evt # type: ignore[attr-defined] ctx = Context(mycharm, meta={'name': 'foo'}, actions={'foo': {}}) @@ -89,8 +91,8 @@ def handle_evt(_: CharmBase, evt): @pytest.mark.parametrize('res_value', ({'a': {'b': {'c'}}}, {'d': 'e'})) -def test_action_event_outputs(mycharm, res_value): - def handle_evt(_: CharmBase, evt: ActionEvent): +def test_action_event_outputs(mycharm: type[CharmBase], res_value: dict[str, Any]) -> None: + def handle_evt(_: CharmBase, evt: ActionEvent) -> None: if not isinstance(evt, ActionEvent): return @@ -99,7 +101,7 @@ def handle_evt(_: CharmBase, evt: ActionEvent): evt.log('log2') evt.fail('failed becozz') - mycharm._evt_handler = handle_evt + mycharm._evt_handler = handle_evt # type: ignore[attr-defined] ctx = Context(mycharm, meta={'name': 'foo'}, actions={'foo': {}}) with pytest.raises(ActionFailed) as exc_info: @@ -109,13 +111,13 @@ def handle_evt(_: CharmBase, evt: ActionEvent): assert ctx.action_logs == ['log1', 'log2'] -def test_action_event_fail(mycharm): - def handle_evt(_: CharmBase, evt: ActionEvent): +def test_action_event_fail(mycharm: type[CharmBase]) -> None: + def handle_evt(_: CharmBase, evt: ActionEvent) -> None: if not isinstance(evt, ActionEvent): return evt.fail('action failed!') - mycharm._evt_handler = handle_evt + mycharm._evt_handler = handle_evt # type: ignore[attr-defined] ctx = Context(mycharm, meta={'name': 'foo'}, actions={'foo': {}}) with pytest.raises(ActionFailed) as exc_info: @@ -123,13 +125,13 @@ def handle_evt(_: CharmBase, evt: ActionEvent): assert exc_info.value.message == 'action failed!' -def test_action_event_fail_context_manager(mycharm): - def handle_evt(_: CharmBase, evt: ActionEvent): +def test_action_event_fail_context_manager(mycharm: type[CharmBase]) -> None: + def handle_evt(_: CharmBase, evt: ActionEvent) -> None: if not isinstance(evt, ActionEvent): return evt.fail('action failed!') - mycharm._evt_handler = handle_evt + mycharm._evt_handler = handle_evt # type: ignore[attr-defined] ctx = Context(mycharm, meta={'name': 'foo'}, actions={'foo': {}}) with pytest.raises(ActionFailed) as exc_info: @@ -138,13 +140,13 @@ def handle_evt(_: CharmBase, evt: ActionEvent): assert exc_info.value.message == 'action failed!' -def test_action_continues_after_fail(): +def test_action_continues_after_fail() -> None: class MyCharm(CharmBase): - def __init__(self, framework): + def __init__(self, framework: Framework): super().__init__(framework) framework.observe(self.on.foo_action, self._on_foo_action) - def _on_foo_action(self, event): + def _on_foo_action(self, event: ActionEvent) -> None: event.log('starting') event.set_results({'initial': 'result'}) event.fail('oh no!') @@ -158,7 +160,7 @@ def _on_foo_action(self, event): assert ctx.action_results == {'initial': 'result', 'final': 'result'} -def _ops_less_than(wanted_major, wanted_minor): +def _ops_less_than(wanted_major: int, wanted_minor: int) -> bool: major, minor = (int(v) for v in ops_version.split('.')[:2]) if major < wanted_major: return True @@ -168,45 +170,45 @@ def _ops_less_than(wanted_major, wanted_minor): @pytest.mark.skipif(_ops_less_than(2, 11), reason="ops 2.10 and earlier don't have ActionEvent.id") -def test_action_event_has_id(mycharm): - def handle_evt(_: CharmBase, evt: ActionEvent): +def test_action_event_has_id(mycharm: type[CharmBase]) -> None: + def handle_evt(_: CharmBase, evt: ActionEvent) -> None: if not isinstance(evt, ActionEvent): return assert isinstance(evt.id, str) and evt.id != '' - mycharm._evt_handler = handle_evt + mycharm._evt_handler = handle_evt # type: ignore[attr-defined] ctx = Context(mycharm, meta={'name': 'foo'}, actions={'foo': {}}) ctx.run(ctx.on.action('foo'), State()) @pytest.mark.skipif(_ops_less_than(2, 11), reason="ops 2.10 and earlier don't have ActionEvent.id") -def test_action_event_has_override_id(mycharm): +def test_action_event_has_override_id(mycharm: type[CharmBase]) -> None: uuid = '0ddba11-cafe-ba1d-5a1e-dec0debad' - def handle_evt(charm: CharmBase, evt: ActionEvent): + def handle_evt(charm: CharmBase, evt: ActionEvent) -> None: if not isinstance(evt, ActionEvent): return assert evt.id == uuid - mycharm._evt_handler = handle_evt + mycharm._evt_handler = handle_evt # type: ignore[attr-defined] ctx = Context(mycharm, meta={'name': 'foo'}, actions={'foo': {}}) ctx.run(ctx.on.action('foo', id=uuid), State()) -def test_two_actions_same_context(): +def test_two_actions_same_context() -> None: class MyCharm(CharmBase): - def __init__(self, framework): + def __init__(self, framework: Framework): super().__init__(framework) framework.observe(self.on.foo_action, self._on_foo_action) framework.observe(self.on.bar_action, self._on_bar_action) - def _on_foo_action(self, event): + def _on_foo_action(self, event: ActionEvent) -> None: event.log('foo') event.set_results({'foo': 'result'}) - def _on_bar_action(self, event): + def _on_bar_action(self, event: ActionEvent) -> None: event.log('bar') event.set_results({'bar': 'result'}) @@ -220,12 +222,12 @@ def _on_bar_action(self, event): assert ctx.action_logs == ['bar'] -def test_positional_arguments(): +def test_positional_arguments() -> None: with pytest.raises(TypeError): - _Action('foo', {}) + _Action('foo', {}) # type: ignore[misc] -def test_default_arguments(): +def test_default_arguments() -> None: expected_id = _next_action_id(update=False) name = 'foo' action = _Action(name) From 58e19b0afb06ac9f6be45f896497e33eeca2c0e9 Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Sat, 13 Dec 2025 00:47:27 +1300 Subject: [PATCH 08/17] test: add type annotations to test_config. --- testing/tests/test_e2e/test_config.py | 35 ++++++++++++++------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/testing/tests/test_e2e/test_config.py b/testing/tests/test_e2e/test_config.py index cb226d59f..f1361c56b 100644 --- a/testing/tests/test_e2e/test_config.py +++ b/testing/tests/test_e2e/test_config.py @@ -1,29 +1,30 @@ from __future__ import annotations +from typing import Any + +import ops import pytest -from ops.charm import CharmBase -from ops.framework import Framework from scenario.state import State from ..helpers import trigger @pytest.fixture(scope='function') -def mycharm(): - class MyCharm(CharmBase): - def __init__(self, framework: Framework): +def mycharm() -> type[ops.CharmBase]: + class MyCharm(ops.CharmBase): + def __init__(self, framework: ops.Framework): super().__init__(framework) for evt in self.on.events().values(): self.framework.observe(evt, self._on_event) - def _on_event(self, event): + def _on_event(self, event: ops.EventBase) -> None: pass return MyCharm -def test_config_get(mycharm): - def check_cfg(charm: CharmBase): +def test_config_get(mycharm: type[ops.CharmBase]) -> None: + def check_cfg(charm: ops.CharmBase) -> None: assert charm.config['foo'] == 'bar' assert charm.config['baz'] == 1 @@ -39,8 +40,8 @@ def check_cfg(charm: CharmBase): ) -def test_config_get_default_from_meta(mycharm): - def check_cfg(charm: CharmBase): +def test_config_get_default_from_meta(mycharm: type[ops.CharmBase]) -> None: + def check_cfg(charm: ops.CharmBase) -> None: assert charm.config['foo'] == 'bar' assert charm.config['baz'] == 2 assert charm.config['qux'] is False @@ -71,18 +72,18 @@ def check_cfg(charm: CharmBase): {'baz': 4, 'foo': 'bar', 'qux': True}, ), ) -def test_config_in_not_mutated(mycharm, cfg_in): - class MyCharm(CharmBase): - def __init__(self, framework: Framework): +def test_config_in_not_mutated(mycharm: type[ops.CharmBase], cfg_in: dict[str, Any]) -> None: + class MyCharm(ops.CharmBase): + def __init__(self, framework: ops.Framework): super().__init__(framework) for evt in self.on.events().values(): self.framework.observe(evt, self._on_event) - def _on_event(self, event): + def _on_event(self, event: ops.EventBase) -> None: # access the config to trigger a config-get - foo_cfg = self.config['foo'] # noqa: F841 - baz_cfg = self.config['baz'] # noqa: F841 - qux_cfg = self.config['qux'] # noqa: F841 + _foo_cfg = self.config['foo'] + _baz_cfg = self.config['baz'] + _qux_cfg = self.config['qux'] state_out = trigger( State( From d82d82ae1faeb9c002eec287cec237ffccb1f579 Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Sat, 13 Dec 2025 00:52:34 +1300 Subject: [PATCH 09/17] test: add type annotations to test_event. --- testing/tests/test_e2e/test_event.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/testing/tests/test_e2e/test_event.py b/testing/tests/test_e2e/test_event.py index 83c0b449f..af6c1d4c8 100644 --- a/testing/tests/test_e2e/test_event.py +++ b/testing/tests/test_e2e/test_event.py @@ -2,7 +2,6 @@ import ops import pytest -from ops import CharmBase from scenario import Context from scenario.state import State, _CharmSpec, _Event, _EventType @@ -30,7 +29,7 @@ ('kaboozle_bar_baz', _EventType.CUSTOM), ), ) -def test_event_type(evt, expected_type): +def test_event_type(evt: str, expected_type: _EventType) -> None: event = _Event(evt) assert event._path.type is expected_type @@ -40,10 +39,10 @@ def test_event_type(evt, expected_type): assert event._is_secret_event is (expected_type is _EventType.SECRET) assert event._is_action_event is (expected_type is _EventType.ACTION) - class MyCharm(CharmBase): + class MyCharm(ops.CharmBase): pass - spec = _CharmSpec( + spec = _CharmSpec[MyCharm]( MyCharm, meta={ 'requires': { @@ -60,8 +59,8 @@ class MyCharm(CharmBase): assert event._is_builtin_event(spec) is (expected_type is not _EventType.CUSTOM) -def test_emitted_framework(): - class MyCharm(CharmBase): +def test_emitted_framework() -> None: + class MyCharm(ops.CharmBase): META = {'name': 'joop'} ctx = Context(MyCharm, meta=MyCharm.META, capture_framework_events=True) @@ -75,15 +74,15 @@ class MyCharm(CharmBase): ] -def test_emitted_deferred(): - class MyCharm(CharmBase): +def test_emitted_deferred() -> None: + class MyCharm(ops.CharmBase): META = {'name': 'joop'} def __init__(self, framework: ops.Framework): super().__init__(framework) framework.observe(self.on.update_status, self._on_update_status) - def _on_update_status(self, _: ops.UpdateStatusEvent): + def _on_update_status(self, _: ops.UpdateStatusEvent) -> None: pass ctx = Context( From eceb259b5dcd3a082986bd4b44dc896e19edcd9c Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Sat, 13 Dec 2025 13:14:48 +1300 Subject: [PATCH 10/17] test: add type annotations to test_deferred. --- testing/tests/test_e2e/test_deferred.py | 85 +++++++++++++------------ 1 file changed, 43 insertions(+), 42 deletions(-) diff --git a/testing/tests/test_e2e/test_deferred.py b/testing/tests/test_e2e/test_deferred.py index 643d96e32..51a14a347 100644 --- a/testing/tests/test_e2e/test_deferred.py +++ b/testing/tests/test_e2e/test_deferred.py @@ -4,7 +4,6 @@ import ops import pytest -from ops.framework import LifecycleEvent from scenario import Context from scenario.state import Container, Relation, State, _Event @@ -14,7 +13,7 @@ @pytest.fixture(scope='function') -def mycharm(): +def mycharm() -> type[ops.CharmBase]: class MyCharm(ops.CharmBase): META: dict[str, typing.Any] = { 'name': 'mycharm', @@ -27,12 +26,12 @@ class MyCharm(ops.CharmBase): def __init__(self, framework: ops.Framework): super().__init__(framework) for evt in self.on.events().values(): - if issubclass(evt.event_type, LifecycleEvent): + if issubclass(evt.event_type, ops.LifecycleEvent): continue self.framework.observe(evt, self._on_event) - def _on_event(self, event: ops.EventBase): + def _on_event(self, event: ops.EventBase) -> None: self.captured.append(event) if self.defer_next > 0: self.defer_next -= 1 @@ -41,21 +40,21 @@ def _on_event(self, event: ops.EventBase): return MyCharm -def test_defer(mycharm): - mycharm.defer_next = True - out = trigger(State(), 'start', mycharm, meta=mycharm.META) +def test_defer(mycharm: type[ops.CharmBase]) -> None: + mycharm.defer_next = True # type: ignore[attr-defined] + out = trigger(State(), 'start', mycharm, meta=mycharm.META) # type: ignore[attr-defined] assert len(out.deferred) == 1 assert out.deferred[0].name == 'start' -def test_deferred_evt_emitted(mycharm): - mycharm.defer_next = 2 +def test_deferred_evt_emitted(mycharm: type[ops.CharmBase]) -> None: + mycharm.defer_next = 2 # type: ignore[attr-defined] out = trigger( - State(deferred=[_Event('update_status').deferred(handler=mycharm._on_event)]), + State(deferred=[_Event('update_status').deferred(handler=mycharm._on_event)]), # type: ignore[attr-defined] 'start', mycharm, - meta=mycharm.META, + meta=mycharm.META, # type: ignore[attr-defined] ) # we deferred the first 2 events we saw: update-status, start. @@ -64,13 +63,13 @@ def test_deferred_evt_emitted(mycharm): assert update_status.name == 'update_status' # we saw start and update-status. - upstat, start = mycharm.captured + upstat, start = mycharm.captured # type: ignore[attr-defined] assert isinstance(upstat, ops.UpdateStatusEvent) assert isinstance(start, ops.StartEvent) -def test_deferred_relation_event(mycharm): - mycharm.defer_next = 2 +def test_deferred_relation_event(mycharm: type[ops.CharmBase]) -> None: + mycharm.defer_next = 2 # type: ignore[attr-defined] rel = Relation(endpoint='foo', remote_app_name='remote') @@ -79,13 +78,13 @@ def test_deferred_relation_event(mycharm): relations={rel}, deferred=[ _Event('foo_relation_changed', relation=rel).deferred( - handler=mycharm._on_event, + handler=mycharm._on_event, # type: ignore[attr-defined] ) ], ), 'start', mycharm, - meta=mycharm.META, + meta=mycharm.META, # type: ignore[attr-defined] ) # we deferred the first 2 events we saw: relation-changed, start. @@ -94,25 +93,25 @@ def test_deferred_relation_event(mycharm): assert start.name == 'start' # we saw start and relation-changed. - relation_changed, start = mycharm.captured + relation_changed, start = mycharm.captured # type: ignore[attr-defined] assert isinstance(relation_changed, ops.RelationChangedEvent) assert isinstance(start, ops.StartEvent) -def test_deferred_relation_event_from_relation(mycharm): - ctx = Context(mycharm, meta=mycharm.META) - mycharm.defer_next = 2 +def test_deferred_relation_event_from_relation(mycharm: type[ops.CharmBase]) -> None: + ctx = Context(mycharm, meta=mycharm.META) # type: ignore[attr-defined] + mycharm.defer_next = 2 # type: ignore[attr-defined] rel = Relation(endpoint='foo', remote_app_name='remote') out = trigger( State( relations={rel}, deferred=[ - ctx.on.relation_changed(rel, remote_unit=1).deferred(handler=mycharm._on_event) + ctx.on.relation_changed(rel, remote_unit=1).deferred(handler=mycharm._on_event) # type: ignore[attr-defined] ], ), 'start', mycharm, - meta=mycharm.META, + meta=mycharm.META, # type: ignore[attr-defined] ) # we deferred the first 2 events we saw: foo_relation_changed, start. @@ -127,13 +126,13 @@ def test_deferred_relation_event_from_relation(mycharm): assert start.name == 'start' # we saw start and foo_relation_changed. - relation_changed, start = mycharm.captured + relation_changed, start = mycharm.captured # type: ignore[attr-defined] assert isinstance(relation_changed, ops.RelationChangedEvent) assert isinstance(start, ops.StartEvent) -def test_deferred_workload_event(mycharm): - mycharm.defer_next = 2 +def test_deferred_workload_event(mycharm: type[ops.CharmBase]) -> None: + mycharm.defer_next = 2 # type: ignore[attr-defined] ctr = Container('foo') @@ -141,12 +140,12 @@ def test_deferred_workload_event(mycharm): State( containers={ctr}, deferred=[ - _Event('foo_pebble_ready', container=ctr).deferred(handler=mycharm._on_event) + _Event('foo_pebble_ready', container=ctr).deferred(handler=mycharm._on_event) # type: ignore[attr-defined] ], ), 'start', mycharm, - meta=mycharm.META, + meta=mycharm.META, # type: ignore[attr-defined] ) # we deferred the first 2 events we saw: foo_pebble_ready, start. @@ -155,18 +154,18 @@ def test_deferred_workload_event(mycharm): assert start.name == 'start' # we saw start and foo_pebble_ready. - ready, start = mycharm.captured + ready, start = mycharm.captured # type: ignore[attr-defined] assert isinstance(ready, ops.WorkloadEvent) assert isinstance(start, ops.StartEvent) -def test_defer_reemit_lifecycle_event(mycharm): - ctx = Context(mycharm, meta=mycharm.META, capture_deferred_events=True) +def test_defer_reemit_lifecycle_event(mycharm: type[ops.CharmBase]) -> None: + ctx = Context(mycharm, meta=mycharm.META, capture_deferred_events=True) # type: ignore[attr-defined] - mycharm.defer_next = 1 + mycharm.defer_next = 1 # type: ignore[attr-defined] state_1 = ctx.run(ctx.on.update_status(), State()) - mycharm.defer_next = 0 + mycharm.defer_next = 0 # type: ignore[attr-defined] state_2 = ctx.run(ctx.on.start(), state_1) assert [type(e).__name__ for e in ctx.emitted_events] == [ @@ -178,14 +177,14 @@ def test_defer_reemit_lifecycle_event(mycharm): assert not state_2.deferred -def test_defer_reemit_relation_event(mycharm): - ctx = Context(mycharm, meta=mycharm.META, capture_deferred_events=True) +def test_defer_reemit_relation_event(mycharm: type[ops.CharmBase]) -> None: + ctx = Context(mycharm, meta=mycharm.META, capture_deferred_events=True) # type: ignore[attr-defined] rel = Relation('foo') - mycharm.defer_next = 1 + mycharm.defer_next = 1 # type: ignore[attr-defined] state_1 = ctx.run(ctx.on.relation_created(rel), State(relations={rel})) - mycharm.defer_next = 0 + mycharm.defer_next = 0 # type: ignore[attr-defined] state_2 = ctx.run(ctx.on.start(), state_1) assert [type(e).__name__ for e in ctx.emitted_events] == [ @@ -228,22 +227,24 @@ def __init__(self, charm: ops.CharmBase): super().__init__(charm, 'my-consumer') -def test_defer_custom_event(mycharm): +def test_defer_custom_event(mycharm: type[ops.CharmBase]) -> None: class MyCharm(mycharm): def __init__(self, framework: ops.Framework): super().__init__(framework) self.consumer = MyConsumer(self) - framework.observe(self.consumer.on.foo_changed, self._on_event) + framework.observe(self.consumer.on.foo_changed, self._on_event) # type: ignore[attr-defined] - ctx = Context(MyCharm, meta=mycharm.META, capture_deferred_events=True) + ctx = Context(MyCharm, meta=mycharm.META, capture_deferred_events=True) # type: ignore[attr-defined] - mycharm.defer_next = 1 - state_1 = ctx.run(ctx.on.custom(MyConsumer.on.foo_changed, 'foo', 28), State()) + mycharm.defer_next = 1 # type: ignore[attr-defined] + state_1 = ctx.run( + ctx.on.custom(typing.cast('typing.Any', MyConsumer.on).foo_changed, 'foo', 28), State() + ) assert [type(e).__name__ for e in ctx.emitted_events] == ['CustomEventWithArgs'] assert ctx.emitted_events[0].snapshot() == {'arg0': 'foo', 'arg1': 28} assert len(state_1.deferred) == 1 - mycharm.defer_next = 0 + mycharm.defer_next = 0 # type: ignore[attr-defined] state_2 = ctx.run(ctx.on.start(), state_1) assert [type(e).__name__ for e in ctx.emitted_events] == [ 'CustomEventWithArgs', From 49893fcd501649c24487661d9cdf84767195f4f4 Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Sat, 13 Dec 2025 15:00:13 +1300 Subject: [PATCH 11/17] test: add tests for test_stored_state. --- testing/tests/test_e2e/test_stored_state.py | 28 ++++++++++----------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/testing/tests/test_e2e/test_stored_state.py b/testing/tests/test_e2e/test_stored_state.py index 39a36c3ce..859de7215 100644 --- a/testing/tests/test_e2e/test_stored_state.py +++ b/testing/tests/test_e2e/test_stored_state.py @@ -1,22 +1,22 @@ from __future__ import annotations -import pytest +from typing import Any import ops -from ops.framework import StoredState as ops_storedstate +import pytest from scenario.state import State, StoredState from tests.helpers import trigger @pytest.fixture(scope='function') -def mycharm(): +def mycharm() -> type[ops.CharmBase]: class MyCharm(ops.CharmBase): META = {'name': 'mycharm'} - _read = {} - _stored = ops_storedstate() - _stored2 = ops_storedstate() + _read: dict[str, Any] = {} + _stored = ops.StoredState() + _stored2 = ops.StoredState() def __init__(self, framework: ops.Framework): super().__init__(framework) @@ -25,15 +25,15 @@ def __init__(self, framework: ops.Framework): for evt in self.on.events().values(): self.framework.observe(evt, self._on_event) - def _on_event(self, _: ops.EventBase): + def _on_event(self, _: ops.EventBase) -> None: self._read['foo'] = self._stored.foo self._read['baz'] = self._stored.baz return MyCharm -def test_stored_state_default(mycharm): - out = trigger(State(), 'start', mycharm, meta=mycharm.META) +def test_stored_state_default(mycharm: type[ops.CharmBase]) -> None: + out = trigger(State(), 'start', mycharm, meta=mycharm.META) # type: ignore[attr-defined] assert out.get_stored_state('_stored', owner_path='MyCharm').content == { 'foo': 'bar', 'baz': {12: 142}, @@ -44,7 +44,7 @@ def test_stored_state_default(mycharm): } -def test_stored_state_initialized(mycharm): +def test_stored_state_initialized(mycharm: type[ops.CharmBase]) -> None: out = trigger( State( stored_states={ @@ -53,7 +53,7 @@ def test_stored_state_initialized(mycharm): ), 'start', mycharm, - meta=mycharm.META, + meta=mycharm.META, # type: ignore[attr-defined] ) assert out.get_stored_state('_stored', owner_path='MyCharm').content == { 'foo': 'FOOX', @@ -65,12 +65,12 @@ def test_stored_state_initialized(mycharm): } -def test_positional_arguments(): +def test_positional_arguments() -> None: with pytest.raises(TypeError): - StoredState('_stored', '') + StoredState('_stored', '') # type: ignore[call-arg] -def test_default_arguments(): +def test_default_arguments() -> None: s = StoredState() assert s.name == '_stored' assert s.owner_path is None From 86ff5363890c8ad43476c15c9d8ae6f4ccd60792 Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Sat, 13 Dec 2025 15:20:21 +1300 Subject: [PATCH 12/17] chore: copy over the exclusions done in the other branch --- pyproject.toml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 980d7e22d..18ef96556 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -272,6 +272,14 @@ builtins-ignorelist = ["id", "min", "map", "range", "type", "TimeoutError", "Con include = ["ops/*.py", "ops/_private/*.py", "test/*.py", "test/charms/*/src/*.py", "testing/src/*.py", "testing/tests/*.py", "testing/tests/test_e2e/*.py"] exclude = [ "tracing/*", + "testing/tests/helpers.py", + "testing/tests/test_charm_spec_autoload.py", + "testing/tests/test_consistency_checker.py", + "testing/tests/test_context_on.py", + "testing/tests/test_context.py", + "testing/tests/test_emitted_events_util.py", + "testing/tests/test_plugin.py", + "testing/tests/test_runtime.py", "testing/tests/test_e2e/test_secrets.py", "testing/tests/test_e2e/test_relations.py", "testing/tests/test_e2e/test_trace_data.py", From eab3f63a6ec7b3944882d6ee60689739582db461 Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Sat, 13 Dec 2025 15:26:16 +1300 Subject: [PATCH 13/17] test: add return annotations (this will clash with the other PR, we'll resolve it later). --- testing/tests/helpers.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/testing/tests/helpers.py b/testing/tests/helpers.py index 913dcbf98..a86b29eef 100644 --- a/testing/tests/helpers.py +++ b/testing/tests/helpers.py @@ -58,7 +58,7 @@ def trigger( return state_out -def jsonpatch_delta(self, other: 'State'): +def jsonpatch_delta(self: 'State', other: 'State') -> list[dict[str, Any]]: dict_other = dataclasses.asdict(other) dict_self = dataclasses.asdict(self) for attr in ( @@ -77,5 +77,8 @@ def jsonpatch_delta(self, other: 'State'): return sort_patch(patch) -def sort_patch(patch: list[dict], key=lambda obj: obj['path'] + obj['op']): +def sort_patch( + patch: list[dict[str, Any]], + key: Callable[[dict[str, Any]], str] = lambda obj: obj['path'] + obj['op'], +) -> list[dict[str, Any]]: return sorted(patch, key=key) From 5f62371d5a84be2605e575a88c520ea02d5b3e51 Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Mon, 15 Dec 2025 14:37:33 +1300 Subject: [PATCH 14/17] chore: remove -> None. --- testing/tests/test_e2e/test_actions.py | 50 +++++----- testing/tests/test_e2e/test_cloud_spec.py | 8 +- testing/tests/test_e2e/test_config.py | 14 +-- testing/tests/test_e2e/test_deferred.py | 18 ++-- testing/tests/test_e2e/test_event.py | 8 +- testing/tests/test_e2e/test_network.py | 12 +-- .../tests/test_e2e/test_play_assertions.py | 14 +-- testing/tests/test_e2e/test_state.py | 92 +++++++++---------- testing/tests/test_e2e/test_stored_state.py | 10 +- testing/tests/test_e2e/test_vroot.py | 6 +- 10 files changed, 115 insertions(+), 117 deletions(-) diff --git a/testing/tests/test_e2e/test_actions.py b/testing/tests/test_e2e/test_actions.py index 5ceec87a0..77d93d5ff 100644 --- a/testing/tests/test_e2e/test_actions.py +++ b/testing/tests/test_e2e/test_actions.py @@ -22,7 +22,7 @@ def __init__(self, framework: Framework): for evt in self.on.events().values(): self.framework.observe(evt, self._on_event) - def _on_event(self, event: ActionEvent) -> None: + def _on_event(self, event: ActionEvent): if handler := self._evt_handler: handler(event) @@ -30,7 +30,7 @@ def _on_event(self, event: ActionEvent) -> None: @pytest.mark.parametrize('baz_value', (True, False)) -def test_action_event(mycharm: type[CharmBase], baz_value: bool) -> None: +def test_action_event(mycharm: type[CharmBase], baz_value: bool): ctx: Context[CharmBase] = Context( mycharm, meta={'name': 'foo'}, @@ -45,13 +45,13 @@ def test_action_event(mycharm: type[CharmBase], baz_value: bool) -> None: assert evt.params['baz'] is baz_value -def test_action_no_results() -> None: +def test_action_no_results(): class MyCharm(CharmBase): def __init__(self, framework: Framework): super().__init__(framework) framework.observe(self.on.act_action, self._on_act_action) - def _on_act_action(self, _: ActionEvent) -> None: + def _on_act_action(self, _: ActionEvent): pass ctx = Context(MyCharm, meta={'name': 'foo'}, actions={'act': {}}) @@ -61,8 +61,8 @@ def _on_act_action(self, _: ActionEvent) -> None: @pytest.mark.parametrize('res_value', ('one', 1, [2], ['bar'], (1,), {1, 2})) -def test_action_event_results_invalid(mycharm: type[CharmBase], res_value: object) -> None: - def handle_evt(charm: CharmBase, evt: ActionEvent) -> None: +def test_action_event_results_invalid(mycharm: type[CharmBase], res_value: object): + def handle_evt(charm: CharmBase, evt: ActionEvent): with pytest.raises((TypeError, AttributeError)): evt.set_results(res_value) # type: ignore[arg-type] @@ -73,8 +73,8 @@ def handle_evt(charm: CharmBase, evt: ActionEvent) -> None: @pytest.mark.parametrize('res_value', ({'a': {'b': {'c'}}}, {'d': 'e'})) -def test_action_event_results_valid(mycharm: type[CharmBase], res_value: dict[str, Any]) -> None: - def handle_evt(_: CharmBase, evt: ActionEvent) -> None: +def test_action_event_results_valid(mycharm: type[CharmBase], res_value: dict[str, Any]): + def handle_evt(_: CharmBase, evt: ActionEvent): if not isinstance(evt, ActionEvent): return evt.set_results(res_value) @@ -91,8 +91,8 @@ def handle_evt(_: CharmBase, evt: ActionEvent) -> None: @pytest.mark.parametrize('res_value', ({'a': {'b': {'c'}}}, {'d': 'e'})) -def test_action_event_outputs(mycharm: type[CharmBase], res_value: dict[str, Any]) -> None: - def handle_evt(_: CharmBase, evt: ActionEvent) -> None: +def test_action_event_outputs(mycharm: type[CharmBase], res_value: dict[str, Any]): + def handle_evt(_: CharmBase, evt: ActionEvent): if not isinstance(evt, ActionEvent): return @@ -111,8 +111,8 @@ def handle_evt(_: CharmBase, evt: ActionEvent) -> None: assert ctx.action_logs == ['log1', 'log2'] -def test_action_event_fail(mycharm: type[CharmBase]) -> None: - def handle_evt(_: CharmBase, evt: ActionEvent) -> None: +def test_action_event_fail(mycharm: type[CharmBase]): + def handle_evt(_: CharmBase, evt: ActionEvent): if not isinstance(evt, ActionEvent): return evt.fail('action failed!') @@ -125,8 +125,8 @@ def handle_evt(_: CharmBase, evt: ActionEvent) -> None: assert exc_info.value.message == 'action failed!' -def test_action_event_fail_context_manager(mycharm: type[CharmBase]) -> None: - def handle_evt(_: CharmBase, evt: ActionEvent) -> None: +def test_action_event_fail_context_manager(mycharm: type[CharmBase]): + def handle_evt(_: CharmBase, evt: ActionEvent): if not isinstance(evt, ActionEvent): return evt.fail('action failed!') @@ -140,13 +140,13 @@ def handle_evt(_: CharmBase, evt: ActionEvent) -> None: assert exc_info.value.message == 'action failed!' -def test_action_continues_after_fail() -> None: +def test_action_continues_after_fail(): class MyCharm(CharmBase): def __init__(self, framework: Framework): super().__init__(framework) framework.observe(self.on.foo_action, self._on_foo_action) - def _on_foo_action(self, event: ActionEvent) -> None: + def _on_foo_action(self, event: ActionEvent): event.log('starting') event.set_results({'initial': 'result'}) event.fail('oh no!') @@ -170,8 +170,8 @@ def _ops_less_than(wanted_major: int, wanted_minor: int) -> bool: @pytest.mark.skipif(_ops_less_than(2, 11), reason="ops 2.10 and earlier don't have ActionEvent.id") -def test_action_event_has_id(mycharm: type[CharmBase]) -> None: - def handle_evt(_: CharmBase, evt: ActionEvent) -> None: +def test_action_event_has_id(mycharm: type[CharmBase]): + def handle_evt(_: CharmBase, evt: ActionEvent): if not isinstance(evt, ActionEvent): return assert isinstance(evt.id, str) and evt.id != '' @@ -183,10 +183,10 @@ def handle_evt(_: CharmBase, evt: ActionEvent) -> None: @pytest.mark.skipif(_ops_less_than(2, 11), reason="ops 2.10 and earlier don't have ActionEvent.id") -def test_action_event_has_override_id(mycharm: type[CharmBase]) -> None: +def test_action_event_has_override_id(mycharm: type[CharmBase]): uuid = '0ddba11-cafe-ba1d-5a1e-dec0debad' - def handle_evt(charm: CharmBase, evt: ActionEvent) -> None: + def handle_evt(charm: CharmBase, evt: ActionEvent): if not isinstance(evt, ActionEvent): return assert evt.id == uuid @@ -197,18 +197,18 @@ def handle_evt(charm: CharmBase, evt: ActionEvent) -> None: ctx.run(ctx.on.action('foo', id=uuid), State()) -def test_two_actions_same_context() -> None: +def test_two_actions_same_context(): class MyCharm(CharmBase): def __init__(self, framework: Framework): super().__init__(framework) framework.observe(self.on.foo_action, self._on_foo_action) framework.observe(self.on.bar_action, self._on_bar_action) - def _on_foo_action(self, event: ActionEvent) -> None: + def _on_foo_action(self, event: ActionEvent): event.log('foo') event.set_results({'foo': 'result'}) - def _on_bar_action(self, event: ActionEvent) -> None: + def _on_bar_action(self, event: ActionEvent): event.log('bar') event.set_results({'bar': 'result'}) @@ -222,12 +222,12 @@ def _on_bar_action(self, event: ActionEvent) -> None: assert ctx.action_logs == ['bar'] -def test_positional_arguments() -> None: +def test_positional_arguments(): with pytest.raises(TypeError): _Action('foo', {}) # type: ignore[misc] -def test_default_arguments() -> None: +def test_default_arguments(): expected_id = _next_action_id(update=False) name = 'foo' action = _Action(name) diff --git a/testing/tests/test_e2e/test_cloud_spec.py b/testing/tests/test_e2e/test_cloud_spec.py index 97583aa94..ec207c7ff 100644 --- a/testing/tests/test_e2e/test_cloud_spec.py +++ b/testing/tests/test_e2e/test_cloud_spec.py @@ -12,11 +12,11 @@ def __init__(self, framework: ops.Framework): for evt in self.on.events().values(): self.framework.observe(evt, self._on_event) - def _on_event(self, event: ops.EventBase) -> None: + def _on_event(self, event: ops.EventBase): pass -def test_get_cloud_spec() -> None: +def test_get_cloud_spec(): scenario_cloud_spec = scenario.CloudSpec( type='lxd', name='localhost', @@ -51,7 +51,7 @@ def test_get_cloud_spec() -> None: assert mgr.charm.model.get_cloud_spec() == expected_cloud_spec -def test_get_cloud_spec_error() -> None: +def test_get_cloud_spec_error(): ctx = scenario.Context(MyCharm, meta={'name': 'foo'}) state = scenario.State(model=scenario.Model(name='lxd-model', type='lxd')) with ctx(ctx.on.start(), state) as mgr: @@ -59,7 +59,7 @@ def test_get_cloud_spec_error() -> None: mgr.charm.model.get_cloud_spec() -def test_get_cloud_spec_untrusted() -> None: +def test_get_cloud_spec_untrusted(): cloud_spec = scenario.CloudSpec(type='lxd', name='localhost') ctx = scenario.Context(MyCharm, meta={'name': 'foo'}) state = scenario.State( diff --git a/testing/tests/test_e2e/test_config.py b/testing/tests/test_e2e/test_config.py index f1361c56b..3076a009e 100644 --- a/testing/tests/test_e2e/test_config.py +++ b/testing/tests/test_e2e/test_config.py @@ -17,14 +17,14 @@ def __init__(self, framework: ops.Framework): for evt in self.on.events().values(): self.framework.observe(evt, self._on_event) - def _on_event(self, event: ops.EventBase) -> None: + def _on_event(self, event: ops.EventBase): pass return MyCharm -def test_config_get(mycharm: type[ops.CharmBase]) -> None: - def check_cfg(charm: ops.CharmBase) -> None: +def test_config_get(mycharm: type[ops.CharmBase]): + def check_cfg(charm: ops.CharmBase): assert charm.config['foo'] == 'bar' assert charm.config['baz'] == 1 @@ -40,8 +40,8 @@ def check_cfg(charm: ops.CharmBase) -> None: ) -def test_config_get_default_from_meta(mycharm: type[ops.CharmBase]) -> None: - def check_cfg(charm: ops.CharmBase) -> None: +def test_config_get_default_from_meta(mycharm: type[ops.CharmBase]): + def check_cfg(charm: ops.CharmBase): assert charm.config['foo'] == 'bar' assert charm.config['baz'] == 2 assert charm.config['qux'] is False @@ -72,14 +72,14 @@ def check_cfg(charm: ops.CharmBase) -> None: {'baz': 4, 'foo': 'bar', 'qux': True}, ), ) -def test_config_in_not_mutated(mycharm: type[ops.CharmBase], cfg_in: dict[str, Any]) -> None: +def test_config_in_not_mutated(mycharm: type[ops.CharmBase], cfg_in: dict[str, Any]): class MyCharm(ops.CharmBase): def __init__(self, framework: ops.Framework): super().__init__(framework) for evt in self.on.events().values(): self.framework.observe(evt, self._on_event) - def _on_event(self, event: ops.EventBase) -> None: + def _on_event(self, event: ops.EventBase): # access the config to trigger a config-get _foo_cfg = self.config['foo'] _baz_cfg = self.config['baz'] diff --git a/testing/tests/test_e2e/test_deferred.py b/testing/tests/test_e2e/test_deferred.py index 51a14a347..cecaaad9e 100644 --- a/testing/tests/test_e2e/test_deferred.py +++ b/testing/tests/test_e2e/test_deferred.py @@ -31,7 +31,7 @@ def __init__(self, framework: ops.Framework): self.framework.observe(evt, self._on_event) - def _on_event(self, event: ops.EventBase) -> None: + def _on_event(self, event: ops.EventBase): self.captured.append(event) if self.defer_next > 0: self.defer_next -= 1 @@ -40,14 +40,14 @@ def _on_event(self, event: ops.EventBase) -> None: return MyCharm -def test_defer(mycharm: type[ops.CharmBase]) -> None: +def test_defer(mycharm: type[ops.CharmBase]): mycharm.defer_next = True # type: ignore[attr-defined] out = trigger(State(), 'start', mycharm, meta=mycharm.META) # type: ignore[attr-defined] assert len(out.deferred) == 1 assert out.deferred[0].name == 'start' -def test_deferred_evt_emitted(mycharm: type[ops.CharmBase]) -> None: +def test_deferred_evt_emitted(mycharm: type[ops.CharmBase]): mycharm.defer_next = 2 # type: ignore[attr-defined] out = trigger( @@ -68,7 +68,7 @@ def test_deferred_evt_emitted(mycharm: type[ops.CharmBase]) -> None: assert isinstance(start, ops.StartEvent) -def test_deferred_relation_event(mycharm: type[ops.CharmBase]) -> None: +def test_deferred_relation_event(mycharm: type[ops.CharmBase]): mycharm.defer_next = 2 # type: ignore[attr-defined] rel = Relation(endpoint='foo', remote_app_name='remote') @@ -98,7 +98,7 @@ def test_deferred_relation_event(mycharm: type[ops.CharmBase]) -> None: assert isinstance(start, ops.StartEvent) -def test_deferred_relation_event_from_relation(mycharm: type[ops.CharmBase]) -> None: +def test_deferred_relation_event_from_relation(mycharm: type[ops.CharmBase]): ctx = Context(mycharm, meta=mycharm.META) # type: ignore[attr-defined] mycharm.defer_next = 2 # type: ignore[attr-defined] rel = Relation(endpoint='foo', remote_app_name='remote') @@ -131,7 +131,7 @@ def test_deferred_relation_event_from_relation(mycharm: type[ops.CharmBase]) -> assert isinstance(start, ops.StartEvent) -def test_deferred_workload_event(mycharm: type[ops.CharmBase]) -> None: +def test_deferred_workload_event(mycharm: type[ops.CharmBase]): mycharm.defer_next = 2 # type: ignore[attr-defined] ctr = Container('foo') @@ -159,7 +159,7 @@ def test_deferred_workload_event(mycharm: type[ops.CharmBase]) -> None: assert isinstance(start, ops.StartEvent) -def test_defer_reemit_lifecycle_event(mycharm: type[ops.CharmBase]) -> None: +def test_defer_reemit_lifecycle_event(mycharm: type[ops.CharmBase]): ctx = Context(mycharm, meta=mycharm.META, capture_deferred_events=True) # type: ignore[attr-defined] mycharm.defer_next = 1 # type: ignore[attr-defined] @@ -177,7 +177,7 @@ def test_defer_reemit_lifecycle_event(mycharm: type[ops.CharmBase]) -> None: assert not state_2.deferred -def test_defer_reemit_relation_event(mycharm: type[ops.CharmBase]) -> None: +def test_defer_reemit_relation_event(mycharm: type[ops.CharmBase]): ctx = Context(mycharm, meta=mycharm.META, capture_deferred_events=True) # type: ignore[attr-defined] rel = Relation('foo') @@ -227,7 +227,7 @@ def __init__(self, charm: ops.CharmBase): super().__init__(charm, 'my-consumer') -def test_defer_custom_event(mycharm: type[ops.CharmBase]) -> None: +def test_defer_custom_event(mycharm: type[ops.CharmBase]): class MyCharm(mycharm): def __init__(self, framework: ops.Framework): super().__init__(framework) diff --git a/testing/tests/test_e2e/test_event.py b/testing/tests/test_e2e/test_event.py index af6c1d4c8..27d1ccc89 100644 --- a/testing/tests/test_e2e/test_event.py +++ b/testing/tests/test_e2e/test_event.py @@ -29,7 +29,7 @@ ('kaboozle_bar_baz', _EventType.CUSTOM), ), ) -def test_event_type(evt: str, expected_type: _EventType) -> None: +def test_event_type(evt: str, expected_type: _EventType): event = _Event(evt) assert event._path.type is expected_type @@ -59,7 +59,7 @@ class MyCharm(ops.CharmBase): assert event._is_builtin_event(spec) is (expected_type is not _EventType.CUSTOM) -def test_emitted_framework() -> None: +def test_emitted_framework(): class MyCharm(ops.CharmBase): META = {'name': 'joop'} @@ -74,7 +74,7 @@ class MyCharm(ops.CharmBase): ] -def test_emitted_deferred() -> None: +def test_emitted_deferred(): class MyCharm(ops.CharmBase): META = {'name': 'joop'} @@ -82,7 +82,7 @@ def __init__(self, framework: ops.Framework): super().__init__(framework) framework.observe(self.on.update_status, self._on_update_status) - def _on_update_status(self, _: ops.UpdateStatusEvent) -> None: + def _on_update_status(self, _: ops.UpdateStatusEvent): pass ctx = Context( diff --git a/testing/tests/test_e2e/test_network.py b/testing/tests/test_e2e/test_network.py index bfb552d08..8d82fceeb 100644 --- a/testing/tests/test_e2e/test_network.py +++ b/testing/tests/test_e2e/test_network.py @@ -26,7 +26,7 @@ def __init__(self, framework: ops.Framework): for evt in self.on.events().values(): self.framework.observe(evt, self._on_event) - def _on_event(self, event: ops.EventBase) -> None: + def _on_event(self, event: ops.EventBase): if MyCharm._call: MyCharm.called = True MyCharm._call(self, event) @@ -34,7 +34,7 @@ def _on_event(self, event: ops.EventBase) -> None: return MyCharm -def test_ip_get(mycharm: type[ops.CharmBase]) -> None: +def test_ip_get(mycharm: type[ops.CharmBase]): ctx: Context[ops.CharmBase] = Context( mycharm, meta={ @@ -85,7 +85,7 @@ def test_ip_get(mycharm: type[ops.CharmBase]) -> None: assert str(network.bind_address) == '4.4.4.4' -def test_no_sub_binding(mycharm: type[ops.CharmBase]) -> None: +def test_no_sub_binding(mycharm: type[ops.CharmBase]): ctx: Context[ops.CharmBase] = Context( mycharm, meta={ @@ -107,7 +107,7 @@ def test_no_sub_binding(mycharm: type[ops.CharmBase]) -> None: mgr.charm.model.get_binding('bar').network # type: ignore[union-attr] -def test_no_relation_error(mycharm: type[ops.CharmBase]) -> None: +def test_no_relation_error(mycharm: type[ops.CharmBase]): """Attempting to call get_binding on a non-existing relation -> RelationNotFoundError""" ctx: Context[ops.CharmBase] = Context( @@ -137,7 +137,7 @@ def test_no_relation_error(mycharm: type[ops.CharmBase]) -> None: mgr.charm.model.get_binding('foo').network # type: ignore[union-attr] -def test_juju_info_network_default(mycharm: type[ops.CharmBase]) -> None: +def test_juju_info_network_default(mycharm: type[ops.CharmBase]): ctx: Context[ops.CharmBase] = Context( mycharm, meta={'name': 'foo'}, @@ -155,7 +155,7 @@ def test_juju_info_network_default(mycharm: type[ops.CharmBase]) -> None: assert str(network.bind_address) == '192.0.2.0' -def test_explicit_juju_info_network_override(mycharm: type[ops.CharmBase]) -> None: +def test_explicit_juju_info_network_override(mycharm: type[ops.CharmBase]): ctx: Context[ops.CharmBase] = Context( mycharm, meta={ diff --git a/testing/tests/test_e2e/test_play_assertions.py b/testing/tests/test_e2e/test_play_assertions.py index 69d20e1fb..84d411a39 100644 --- a/testing/tests/test_e2e/test_play_assertions.py +++ b/testing/tests/test_e2e/test_play_assertions.py @@ -22,7 +22,7 @@ def __init__(self, framework: ops.Framework): for evt in self.on.events().values(): self.framework.observe(evt, self._on_event) - def _on_event(self, event: ops.EventBase) -> None: + def _on_event(self, event: ops.EventBase): if MyCharm._call: MyCharm.called = True MyCharm._call(self, event) @@ -30,16 +30,16 @@ def _on_event(self, event: ops.EventBase) -> None: return MyCharm -def test_charm_heals_on_start(mycharm: type[ops.CharmBase]) -> None: - def pre_event(charm: ops.CharmBase) -> None: +def test_charm_heals_on_start(mycharm: type[ops.CharmBase]): + def pre_event(charm: ops.CharmBase): assert charm.unit.status == ops.BlockedStatus('foo') assert not charm.called # type: ignore[attr-defined] - def call(charm: ops.CharmBase, _: ops.EventBase) -> None: + def call(charm: ops.CharmBase, _: ops.EventBase): if charm.unit.status.message == 'foo': charm.unit.status = ops.ActiveStatus('yabadoodle') - def post_event(charm: ops.CharmBase) -> None: + def post_event(charm: ops.CharmBase): assert charm.unit.status == ops.ActiveStatus('yabadoodle') assert charm.called # type: ignore[attr-defined] @@ -74,10 +74,10 @@ def post_event(charm: ops.CharmBase) -> None: ] -def test_relation_data_access(mycharm: type[ops.CharmBase]) -> None: +def test_relation_data_access(mycharm: type[ops.CharmBase]): mycharm._call = lambda *_: True # type: ignore[misc] - def check_relation_data(charm: ops.CharmBase) -> None: + def check_relation_data(charm: ops.CharmBase): foo_relations = charm.model.relations['relation_test'] assert len(foo_relations) == 1 foo_rel = foo_relations[0] diff --git a/testing/tests/test_e2e/test_state.py b/testing/tests/test_e2e/test_state.py index 14047c71b..594adca1d 100644 --- a/testing/tests/test_e2e/test_state.py +++ b/testing/tests/test_e2e/test_state.py @@ -74,7 +74,7 @@ def __init__(self, framework: Framework): for evt in self.on.events().values(): self.framework.observe(evt, self._on_event) - def _on_event(self, event: EventBase) -> None: + def _on_event(self, event: EventBase): if self._call: MyCharm.called = True self._call(event) @@ -87,7 +87,7 @@ def state() -> State: return State(config={'foo': 'bar'}, leader=True) -def test_bare_event(state: State, mycharm: type[CharmBase]) -> None: +def test_bare_event(state: State, mycharm: type[CharmBase]): out = trigger( state, 'start', @@ -99,8 +99,8 @@ def test_bare_event(state: State, mycharm: type[CharmBase]) -> None: assert jsonpatch_delta(state, out_purged) == [] -def test_leader_get(state: State, mycharm: type[CharmBase]) -> None: - def pre_event(charm: CharmBase) -> None: +def test_leader_get(state: State, mycharm: type[CharmBase]): + def pre_event(charm: CharmBase): assert charm.unit.is_leader() trigger( @@ -113,8 +113,8 @@ def pre_event(charm: CharmBase) -> None: ) -def test_status_setting(state: State, mycharm: type[CharmBase]) -> None: - def call(charm: CharmBase, e: EventBase) -> None: +def test_status_setting(state: State, mycharm: type[CharmBase]): + def call(charm: CharmBase, e: EventBase): if isinstance(e, CollectStatusEvent): return @@ -145,8 +145,8 @@ def call(charm: CharmBase, e: EventBase) -> None: @pytest.mark.parametrize('connect', (True, False)) -def test_container(connect: bool, mycharm: type[CharmBase]) -> None: - def pre_event(charm: CharmBase) -> None: +def test_container(connect: bool, mycharm: type[CharmBase]): + def pre_event(charm: CharmBase): container = charm.unit.get_container('foo') assert container is not None assert container.name == 'foo' @@ -164,8 +164,8 @@ def pre_event(charm: CharmBase) -> None: ) -def test_relation_get(mycharm: type[CharmBase]) -> None: - def pre_event(charm: CharmBase) -> None: +def test_relation_get(mycharm: type[CharmBase]): + def pre_event(charm: CharmBase): rel = charm.model.get_relation('foo') assert rel is not None assert rel.data[charm.app]['a'] == 'because' @@ -206,8 +206,8 @@ def pre_event(charm: CharmBase) -> None: ) -def test_relation_set(mycharm: type[CharmBase]) -> None: - def event_handler(charm: CharmBase, _: EventBase) -> None: +def test_relation_set(mycharm: type[CharmBase]): + def event_handler(charm: CharmBase, _: EventBase): rel = charm.model.get_relation('foo') assert rel is not None rel.data[charm.app]['a'] = 'b' @@ -222,7 +222,7 @@ def event_handler(charm: CharmBase, _: EventBase) -> None: assert charm.unit.is_leader() - def pre_event(charm: CharmBase) -> None: + def pre_event(charm: CharmBase): assert charm.model.get_relation('foo') assert charm.model.app.planned_units() == 4 @@ -273,7 +273,7 @@ def pre_event(charm: CharmBase) -> None: } -def test_checkinfo_changeid_none() -> None: +def test_checkinfo_changeid_none(): info = CheckInfo('foo', change_id=None) assert info.change_id, 'None should result in a random change_id' info2 = CheckInfo('foo') # None is also the default. @@ -281,7 +281,7 @@ def test_checkinfo_changeid_none() -> None: @pytest.mark.parametrize('id', ('', '28')) -def test_checkinfo_changeid(id: str | None) -> None: +def test_checkinfo_changeid(id: str | None): info = CheckInfo('foo', change_id=ops.pebble.ChangeID(id)) assert info.change_id == ops.pebble.ChangeID(id) @@ -296,24 +296,24 @@ def test_checkinfo_changeid(id: str | None) -> None: (Network, (0, 3)), ], ) -def test_positional_arguments(klass: type[Any], num_args: tuple[int, ...]) -> None: +def test_positional_arguments(klass: type[Any], num_args: tuple[int, ...]): for num in num_args: args = (None,) * num with pytest.raises(TypeError): klass(*args) -def test_model_positional_arguments() -> None: +def test_model_positional_arguments(): with pytest.raises(TypeError): Model('', '') # type: ignore[misc] -def test_container_positional_arguments() -> None: +def test_container_positional_arguments(): with pytest.raises(TypeError): Container('', True) # type: ignore[misc] -def test_container_default_values() -> None: +def test_container_default_values(): name = 'foo' container = Container(name) assert container.name == name @@ -326,7 +326,7 @@ def test_container_default_values() -> None: assert container._base_plan == {} -def test_state_default_values() -> None: +def test_state_default_values(): state = State() assert state.config == {} assert state.relations == frozenset() @@ -345,7 +345,7 @@ def test_state_default_values() -> None: assert state.workload_version == '' -def test_deepcopy_state() -> None: +def test_deepcopy_state(): containers = [Container('foo'), Container('bar')] state = State(containers=containers) state_copy = copy.deepcopy(state) @@ -354,7 +354,7 @@ def test_deepcopy_state() -> None: assert container.name == copied_container.name -def test_replace_state() -> None: +def test_replace_state(): containers = [Container('foo'), Container('bar')] state = State(containers=containers, leader=True) state2 = replace(state, leader=False) @@ -388,7 +388,7 @@ def test_replace_state() -> None: ) def test_immutable_content_dict( component: type[object], attribute: str, required_args: dict[str, Any] -) -> None: +): content = {'foo': 'bar'} obj1 = component(**required_args, **{attribute: content}) obj2 = component(**required_args, **{attribute: content}) @@ -416,7 +416,7 @@ def test_immutable_content_dict( ) def test_immutable_content_list( component: type[object], attribute: str, required_args: dict[str, Any] -) -> None: +): content = ['foo', 'bar'] obj1 = component(**required_args, **{attribute: content}) obj2 = component(**required_args, **{attribute: content}) @@ -440,7 +440,7 @@ def test_immutable_content_list( ) def test_immutable_content_dict_of_dicts( component: type[object], attribute: str, required_args: dict[str, Any] -) -> None: +): content = {0: {'foo': 'bar'}, 1: {'baz': 'qux'}} obj1 = component(**required_args, **{attribute: content}) obj2 = component(**required_args, **{attribute: content}) @@ -477,7 +477,7 @@ def test_immutable_content_dict_of_dicts( ) def test_state_immutable( obj_in: Any, attribute: str, get_method: str, key_attr: str, mycharm: type[CharmBase] -) -> None: +): kwargs: dict[str, Any] = {attribute: obj_in if isinstance(obj_in, dict) else [obj_in]} state_in = State(**kwargs) @@ -532,8 +532,8 @@ def test_state_immutable( ) def test_state_immutable_with_changed_data_relation( relation_type: type[RelationBase], mycharm: type[CharmBase] -) -> None: - def event_handler(charm: CharmBase, _: EventBase) -> None: +): + def event_handler(charm: CharmBase, _: EventBase): rel = charm.model.get_relation(relation_type.__name__) assert rel is not None rel.data[charm.app]['a'] = 'b' @@ -565,7 +565,7 @@ def event_handler(charm: CharmBase, _: EventBase) -> None: assert relation_out.local_unit_data == {'c': 'd', **_DEFAULT_JUJU_DATABAG} -def test_state_immutable_with_changed_data_container(mycharm: type[CharmBase]) -> None: +def test_state_immutable_with_changed_data_container(mycharm: type[CharmBase]): layer_name = 'my-layer' layer = ops.pebble.Layer({ 'services': { @@ -576,7 +576,7 @@ def test_state_immutable_with_changed_data_container(mycharm: type[CharmBase]) - } }) - def event_handler(charm: CharmBase, _: EventBase) -> None: + def event_handler(charm: CharmBase, _: EventBase): container = charm.model.unit.get_container('foo') container.add_layer(layer_name, layer, combine=True) @@ -600,8 +600,8 @@ def event_handler(charm: CharmBase, _: EventBase) -> None: assert container_out.layers == {layer_name: layer} -def test_state_immutable_with_changed_data_ports(mycharm: type[CharmBase]) -> None: - def event_handler(charm: CharmBase, _: EventBase) -> None: +def test_state_immutable_with_changed_data_ports(mycharm: type[CharmBase]): + def event_handler(charm: CharmBase, _: EventBase): charm.model.unit.open_port(protocol='tcp', port=80) mycharm._call = event_handler # type: ignore[attr-defined] @@ -618,8 +618,8 @@ def event_handler(charm: CharmBase, _: EventBase) -> None: assert state_out.opened_ports == {TCPPort(80)} -def test_state_immutable_with_changed_data_secret(mycharm: type[CharmBase]) -> None: - def event_handler(charm: CharmBase, _: EventBase) -> None: +def test_state_immutable_with_changed_data_secret(mycharm: type[CharmBase]): + def event_handler(charm: CharmBase, _: EventBase): secret = charm.model.get_secret(label='my-secret') secret.set_content({'password': 'bar'}) @@ -640,7 +640,7 @@ def event_handler(charm: CharmBase, _: EventBase) -> None: assert secret_out.latest_content == {'password': 'bar'} -def test_state_immutable_with_changed_data_stored_state() -> None: +def test_state_immutable_with_changed_data_stored_state(): class MyCharm(ops.CharmBase): _stored = ops.StoredState() @@ -758,7 +758,7 @@ def _on_start(self, event: ops.StartEvent): ), ], ) -def test_layer_from_rockcraft(rockcraft: dict[str, Any]) -> None: +def test_layer_from_rockcraft(rockcraft: dict[str, Any]): with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml') as f: yaml.safe_dump(rockcraft, f) rockcraft_path = f.name @@ -795,7 +795,7 @@ def test_layer_from_rockcraft(rockcraft: dict[str, Any]) -> None: assert layer_check.tcp['port'] == check['tcp']['port'] -def test_layer_from_rockcraft_safe() -> None: +def test_layer_from_rockcraft_safe(): dangerous_yaml = """ !!python/object/apply:os.system ["echo unsafe"] """ @@ -807,7 +807,7 @@ def test_layer_from_rockcraft_safe() -> None: layer_from_rockcraft(rockcraft_path) -def test_state_from_context() -> None: +def test_state_from_context(): meta: dict[str, Any] = { 'name': 'sam', 'containers': {'container': {}}, @@ -845,7 +845,7 @@ class Charm(ops.CharmBase): assert state.get_stored_state('_stored', owner_path='Charm').name == '_stored' -def test_state_from_context_extend() -> None: +def test_state_from_context_extend(): meta: dict[str, Any] = { 'name': 'sam', 'containers': {'container': {}}, @@ -899,7 +899,7 @@ class Charm(ops.CharmBase): assert state.get_stored_state('_stored', owner_path='Charm').content == {'foo': 'bar'} -def test_state_from_context_merge_config() -> None: +def test_state_from_context_merge_config(): ctx = Context( ops.CharmBase, meta={'name': 'alex'}, @@ -913,9 +913,7 @@ def test_state_from_context_merge_config() -> None: 'rel_type,endpoint', [(Relation, 'relreq'), (PeerRelation, 'peer'), (SubordinateRelation, 'sub')], ) -def test_state_from_context_skip_exiting_relation( - rel_type: type[RelationBase], endpoint: str -) -> None: +def test_state_from_context_skip_exiting_relation(rel_type: type[RelationBase], endpoint: str): meta: dict[str, Any] = { 'name': 'sam', 'peers': {'peer': {'interface': 'friend'}}, @@ -932,7 +930,7 @@ def test_state_from_context_skip_exiting_relation( assert state.get_relation(rel.id).local_app_data == {'a': 'b'} -def test_state_from_context_skip_exiting_container() -> None: +def test_state_from_context_skip_exiting_container(): meta: dict[str, Any] = { 'name': 'sam', 'containers': {'container': {}}, @@ -947,7 +945,7 @@ def test_state_from_context_skip_exiting_container() -> None: assert state.get_container(container.name).can_connect is False -def test_state_from_context_skip_exiting_storage() -> None: +def test_state_from_context_skip_exiting_storage(): meta: dict[str, Any] = { 'name': 'sam', 'storage': {'storage': {}}, @@ -964,7 +962,7 @@ def test_state_from_context_skip_exiting_storage() -> None: assert _next_storage_index(update=False) == next_index -def test_state_from_context_skip_exiting_stored_state() -> None: +def test_state_from_context_skip_exiting_stored_state(): class Charm(ops.CharmBase): _stored = ops.StoredState() @@ -986,7 +984,7 @@ def _make_generator(items: Iterable[Any]) -> Generator[Any]: @pytest.mark.parametrize('iterable', [frozenset, tuple, list, _make_generator]) -def test_state_from_non_sets(iterable: Callable[..., Any]) -> None: +def test_state_from_non_sets(iterable: Callable[..., Any]): meta: dict[str, Any] = { 'name': 'sam', 'containers': {'container': {}}, diff --git a/testing/tests/test_e2e/test_stored_state.py b/testing/tests/test_e2e/test_stored_state.py index 859de7215..7c4634715 100644 --- a/testing/tests/test_e2e/test_stored_state.py +++ b/testing/tests/test_e2e/test_stored_state.py @@ -25,14 +25,14 @@ def __init__(self, framework: ops.Framework): for evt in self.on.events().values(): self.framework.observe(evt, self._on_event) - def _on_event(self, _: ops.EventBase) -> None: + def _on_event(self, _: ops.EventBase): self._read['foo'] = self._stored.foo self._read['baz'] = self._stored.baz return MyCharm -def test_stored_state_default(mycharm: type[ops.CharmBase]) -> None: +def test_stored_state_default(mycharm: type[ops.CharmBase]): out = trigger(State(), 'start', mycharm, meta=mycharm.META) # type: ignore[attr-defined] assert out.get_stored_state('_stored', owner_path='MyCharm').content == { 'foo': 'bar', @@ -44,7 +44,7 @@ def test_stored_state_default(mycharm: type[ops.CharmBase]) -> None: } -def test_stored_state_initialized(mycharm: type[ops.CharmBase]) -> None: +def test_stored_state_initialized(mycharm: type[ops.CharmBase]): out = trigger( State( stored_states={ @@ -65,12 +65,12 @@ def test_stored_state_initialized(mycharm: type[ops.CharmBase]) -> None: } -def test_positional_arguments() -> None: +def test_positional_arguments(): with pytest.raises(TypeError): StoredState('_stored', '') # type: ignore[call-arg] -def test_default_arguments() -> None: +def test_default_arguments(): s = StoredState() assert s.name == '_stored' assert s.owner_path is None diff --git a/testing/tests/test_e2e/test_vroot.py b/testing/tests/test_e2e/test_vroot.py index 8deb45510..6d90cb6c5 100644 --- a/testing/tests/test_e2e/test_vroot.py +++ b/testing/tests/test_e2e/test_vroot.py @@ -42,7 +42,7 @@ def charm_virtual_root() -> Generator[Path]: yield t -def test_charm_virtual_root(charm_virtual_root: Path) -> None: +def test_charm_virtual_root(charm_virtual_root: Path): out = trigger( State(), 'start', @@ -53,7 +53,7 @@ def test_charm_virtual_root(charm_virtual_root: Path) -> None: assert out.unit_status == ActiveStatus('hello world') -def test_charm_virtual_root_cleanup_if_exists(charm_virtual_root: Path) -> None: +def test_charm_virtual_root_cleanup_if_exists(charm_virtual_root: Path): meta_file = charm_virtual_root / 'metadata.yaml' raw_ori_meta = yaml.safe_dump({'name': 'karl'}) meta_file.write_text(raw_ori_meta) @@ -74,7 +74,7 @@ def test_charm_virtual_root_cleanup_if_exists(charm_virtual_root: Path) -> None: assert meta_file.exists() -def test_charm_virtual_root_cleanup_if_not_exists(charm_virtual_root: Path) -> None: +def test_charm_virtual_root_cleanup_if_not_exists(charm_virtual_root: Path): meta_file = charm_virtual_root / 'metadata.yaml' assert not meta_file.exists() From c43500eb5e17341b91e1b064db066a7983f0d396 Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Mon, 15 Dec 2025 14:40:16 +1300 Subject: [PATCH 15/17] chore: remove unnecessary quotes. --- testing/tests/helpers.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/testing/tests/helpers.py b/testing/tests/helpers.py index a86b29eef..c8c097bf1 100644 --- a/testing/tests/helpers.py +++ b/testing/tests/helpers.py @@ -20,7 +20,7 @@ def trigger( - state: 'State', + state: State, event: str | '_Event', charm_type: type['CharmType'], pre_event: Callable[['CharmType'], None] | None = None, @@ -30,7 +30,7 @@ def trigger( config: dict[str, Any] | None = None, charm_root: str | Path | None = None, juju_version: str = _DEFAULT_JUJU_VERSION, -) -> 'State': +) -> State: ctx = Context( charm_type=charm_type, meta=meta, @@ -58,7 +58,7 @@ def trigger( return state_out -def jsonpatch_delta(self: 'State', other: 'State') -> list[dict[str, Any]]: +def jsonpatch_delta(self: State, other: State) -> list[dict[str, Any]]: dict_other = dataclasses.asdict(other) dict_self = dataclasses.asdict(self) for attr in ( From f45fcb8abfd45e4ceefa60851dfd53829f7f0073 Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Mon, 15 Dec 2025 14:45:58 +1300 Subject: [PATCH 16/17] chore: remove scope from type: ignore --- testing/tests/test_e2e/test_actions.py | 18 +++--- testing/tests/test_e2e/test_deferred.py | 58 +++++++++---------- testing/tests/test_e2e/test_network.py | 4 +- .../tests/test_e2e/test_play_assertions.py | 8 +-- testing/tests/test_e2e/test_state.py | 22 +++---- testing/tests/test_e2e/test_stored_state.py | 6 +- 6 files changed, 58 insertions(+), 58 deletions(-) diff --git a/testing/tests/test_e2e/test_actions.py b/testing/tests/test_e2e/test_actions.py index 77d93d5ff..2b1d1f5e6 100644 --- a/testing/tests/test_e2e/test_actions.py +++ b/testing/tests/test_e2e/test_actions.py @@ -64,9 +64,9 @@ def _on_act_action(self, _: ActionEvent): def test_action_event_results_invalid(mycharm: type[CharmBase], res_value: object): def handle_evt(charm: CharmBase, evt: ActionEvent): with pytest.raises((TypeError, AttributeError)): - evt.set_results(res_value) # type: ignore[arg-type] + evt.set_results(res_value) # type: ignore - mycharm._evt_handler = handle_evt # type: ignore[attr-defined] + mycharm._evt_handler = handle_evt # type: ignore ctx = Context(mycharm, meta={'name': 'foo'}, actions={'foo': {}}) ctx.run(ctx.on.action('foo'), State()) @@ -81,7 +81,7 @@ def handle_evt(_: CharmBase, evt: ActionEvent): evt.log('foo') evt.log('bar') - mycharm._evt_handler = handle_evt # type: ignore[attr-defined] + mycharm._evt_handler = handle_evt # type: ignore ctx = Context(mycharm, meta={'name': 'foo'}, actions={'foo': {}}) @@ -101,7 +101,7 @@ def handle_evt(_: CharmBase, evt: ActionEvent): evt.log('log2') evt.fail('failed becozz') - mycharm._evt_handler = handle_evt # type: ignore[attr-defined] + mycharm._evt_handler = handle_evt # type: ignore ctx = Context(mycharm, meta={'name': 'foo'}, actions={'foo': {}}) with pytest.raises(ActionFailed) as exc_info: @@ -117,7 +117,7 @@ def handle_evt(_: CharmBase, evt: ActionEvent): return evt.fail('action failed!') - mycharm._evt_handler = handle_evt # type: ignore[attr-defined] + mycharm._evt_handler = handle_evt # type: ignore ctx = Context(mycharm, meta={'name': 'foo'}, actions={'foo': {}}) with pytest.raises(ActionFailed) as exc_info: @@ -131,7 +131,7 @@ def handle_evt(_: CharmBase, evt: ActionEvent): return evt.fail('action failed!') - mycharm._evt_handler = handle_evt # type: ignore[attr-defined] + mycharm._evt_handler = handle_evt # type: ignore ctx = Context(mycharm, meta={'name': 'foo'}, actions={'foo': {}}) with pytest.raises(ActionFailed) as exc_info: @@ -176,7 +176,7 @@ def handle_evt(_: CharmBase, evt: ActionEvent): return assert isinstance(evt.id, str) and evt.id != '' - mycharm._evt_handler = handle_evt # type: ignore[attr-defined] + mycharm._evt_handler = handle_evt # type: ignore ctx = Context(mycharm, meta={'name': 'foo'}, actions={'foo': {}}) ctx.run(ctx.on.action('foo'), State()) @@ -191,7 +191,7 @@ def handle_evt(charm: CharmBase, evt: ActionEvent): return assert evt.id == uuid - mycharm._evt_handler = handle_evt # type: ignore[attr-defined] + mycharm._evt_handler = handle_evt # type: ignore ctx = Context(mycharm, meta={'name': 'foo'}, actions={'foo': {}}) ctx.run(ctx.on.action('foo', id=uuid), State()) @@ -224,7 +224,7 @@ def _on_bar_action(self, event: ActionEvent): def test_positional_arguments(): with pytest.raises(TypeError): - _Action('foo', {}) # type: ignore[misc] + _Action('foo', {}) # type: ignore def test_default_arguments(): diff --git a/testing/tests/test_e2e/test_deferred.py b/testing/tests/test_e2e/test_deferred.py index cecaaad9e..df6f963cb 100644 --- a/testing/tests/test_e2e/test_deferred.py +++ b/testing/tests/test_e2e/test_deferred.py @@ -41,20 +41,20 @@ def _on_event(self, event: ops.EventBase): def test_defer(mycharm: type[ops.CharmBase]): - mycharm.defer_next = True # type: ignore[attr-defined] - out = trigger(State(), 'start', mycharm, meta=mycharm.META) # type: ignore[attr-defined] + mycharm.defer_next = True # type: ignore + out = trigger(State(), 'start', mycharm, meta=mycharm.META) # type: ignore assert len(out.deferred) == 1 assert out.deferred[0].name == 'start' def test_deferred_evt_emitted(mycharm: type[ops.CharmBase]): - mycharm.defer_next = 2 # type: ignore[attr-defined] + mycharm.defer_next = 2 # type: ignore out = trigger( - State(deferred=[_Event('update_status').deferred(handler=mycharm._on_event)]), # type: ignore[attr-defined] + State(deferred=[_Event('update_status').deferred(handler=mycharm._on_event)]), # type: ignore 'start', mycharm, - meta=mycharm.META, # type: ignore[attr-defined] + meta=mycharm.META, # type: ignore ) # we deferred the first 2 events we saw: update-status, start. @@ -63,13 +63,13 @@ def test_deferred_evt_emitted(mycharm: type[ops.CharmBase]): assert update_status.name == 'update_status' # we saw start and update-status. - upstat, start = mycharm.captured # type: ignore[attr-defined] + upstat, start = mycharm.captured # type: ignore assert isinstance(upstat, ops.UpdateStatusEvent) assert isinstance(start, ops.StartEvent) def test_deferred_relation_event(mycharm: type[ops.CharmBase]): - mycharm.defer_next = 2 # type: ignore[attr-defined] + mycharm.defer_next = 2 # type: ignore rel = Relation(endpoint='foo', remote_app_name='remote') @@ -78,13 +78,13 @@ def test_deferred_relation_event(mycharm: type[ops.CharmBase]): relations={rel}, deferred=[ _Event('foo_relation_changed', relation=rel).deferred( - handler=mycharm._on_event, # type: ignore[attr-defined] + handler=mycharm._on_event, # type: ignore ) ], ), 'start', mycharm, - meta=mycharm.META, # type: ignore[attr-defined] + meta=mycharm.META, # type: ignore ) # we deferred the first 2 events we saw: relation-changed, start. @@ -93,25 +93,25 @@ def test_deferred_relation_event(mycharm: type[ops.CharmBase]): assert start.name == 'start' # we saw start and relation-changed. - relation_changed, start = mycharm.captured # type: ignore[attr-defined] + relation_changed, start = mycharm.captured # type: ignore assert isinstance(relation_changed, ops.RelationChangedEvent) assert isinstance(start, ops.StartEvent) def test_deferred_relation_event_from_relation(mycharm: type[ops.CharmBase]): - ctx = Context(mycharm, meta=mycharm.META) # type: ignore[attr-defined] - mycharm.defer_next = 2 # type: ignore[attr-defined] + ctx = Context(mycharm, meta=mycharm.META) # type: ignore + mycharm.defer_next = 2 # type: ignore rel = Relation(endpoint='foo', remote_app_name='remote') out = trigger( State( relations={rel}, deferred=[ - ctx.on.relation_changed(rel, remote_unit=1).deferred(handler=mycharm._on_event) # type: ignore[attr-defined] + ctx.on.relation_changed(rel, remote_unit=1).deferred(handler=mycharm._on_event) # type: ignore ], ), 'start', mycharm, - meta=mycharm.META, # type: ignore[attr-defined] + meta=mycharm.META, # type: ignore ) # we deferred the first 2 events we saw: foo_relation_changed, start. @@ -126,13 +126,13 @@ def test_deferred_relation_event_from_relation(mycharm: type[ops.CharmBase]): assert start.name == 'start' # we saw start and foo_relation_changed. - relation_changed, start = mycharm.captured # type: ignore[attr-defined] + relation_changed, start = mycharm.captured # type: ignore assert isinstance(relation_changed, ops.RelationChangedEvent) assert isinstance(start, ops.StartEvent) def test_deferred_workload_event(mycharm: type[ops.CharmBase]): - mycharm.defer_next = 2 # type: ignore[attr-defined] + mycharm.defer_next = 2 # type: ignore ctr = Container('foo') @@ -140,12 +140,12 @@ def test_deferred_workload_event(mycharm: type[ops.CharmBase]): State( containers={ctr}, deferred=[ - _Event('foo_pebble_ready', container=ctr).deferred(handler=mycharm._on_event) # type: ignore[attr-defined] + _Event('foo_pebble_ready', container=ctr).deferred(handler=mycharm._on_event) # type: ignore ], ), 'start', mycharm, - meta=mycharm.META, # type: ignore[attr-defined] + meta=mycharm.META, # type: ignore ) # we deferred the first 2 events we saw: foo_pebble_ready, start. @@ -154,18 +154,18 @@ def test_deferred_workload_event(mycharm: type[ops.CharmBase]): assert start.name == 'start' # we saw start and foo_pebble_ready. - ready, start = mycharm.captured # type: ignore[attr-defined] + ready, start = mycharm.captured # type: ignore assert isinstance(ready, ops.WorkloadEvent) assert isinstance(start, ops.StartEvent) def test_defer_reemit_lifecycle_event(mycharm: type[ops.CharmBase]): - ctx = Context(mycharm, meta=mycharm.META, capture_deferred_events=True) # type: ignore[attr-defined] + ctx = Context(mycharm, meta=mycharm.META, capture_deferred_events=True) # type: ignore - mycharm.defer_next = 1 # type: ignore[attr-defined] + mycharm.defer_next = 1 # type: ignore state_1 = ctx.run(ctx.on.update_status(), State()) - mycharm.defer_next = 0 # type: ignore[attr-defined] + mycharm.defer_next = 0 # type: ignore state_2 = ctx.run(ctx.on.start(), state_1) assert [type(e).__name__ for e in ctx.emitted_events] == [ @@ -178,13 +178,13 @@ def test_defer_reemit_lifecycle_event(mycharm: type[ops.CharmBase]): def test_defer_reemit_relation_event(mycharm: type[ops.CharmBase]): - ctx = Context(mycharm, meta=mycharm.META, capture_deferred_events=True) # type: ignore[attr-defined] + ctx = Context(mycharm, meta=mycharm.META, capture_deferred_events=True) # type: ignore rel = Relation('foo') - mycharm.defer_next = 1 # type: ignore[attr-defined] + mycharm.defer_next = 1 # type: ignore state_1 = ctx.run(ctx.on.relation_created(rel), State(relations={rel})) - mycharm.defer_next = 0 # type: ignore[attr-defined] + mycharm.defer_next = 0 # type: ignore state_2 = ctx.run(ctx.on.start(), state_1) assert [type(e).__name__ for e in ctx.emitted_events] == [ @@ -232,11 +232,11 @@ class MyCharm(mycharm): def __init__(self, framework: ops.Framework): super().__init__(framework) self.consumer = MyConsumer(self) - framework.observe(self.consumer.on.foo_changed, self._on_event) # type: ignore[attr-defined] + framework.observe(self.consumer.on.foo_changed, self._on_event) # type: ignore - ctx = Context(MyCharm, meta=mycharm.META, capture_deferred_events=True) # type: ignore[attr-defined] + ctx = Context(MyCharm, meta=mycharm.META, capture_deferred_events=True) # type: ignore - mycharm.defer_next = 1 # type: ignore[attr-defined] + mycharm.defer_next = 1 # type: ignore state_1 = ctx.run( ctx.on.custom(typing.cast('typing.Any', MyConsumer.on).foo_changed, 'foo', 28), State() ) @@ -244,7 +244,7 @@ def __init__(self, framework: ops.Framework): assert ctx.emitted_events[0].snapshot() == {'arg0': 'foo', 'arg1': 28} assert len(state_1.deferred) == 1 - mycharm.defer_next = 0 # type: ignore[attr-defined] + mycharm.defer_next = 0 # type: ignore state_2 = ctx.run(ctx.on.start(), state_1) assert [type(e).__name__ for e in ctx.emitted_events] == [ 'CustomEventWithArgs', diff --git a/testing/tests/test_e2e/test_network.py b/testing/tests/test_e2e/test_network.py index 8d82fceeb..ce4f0d710 100644 --- a/testing/tests/test_e2e/test_network.py +++ b/testing/tests/test_e2e/test_network.py @@ -104,7 +104,7 @@ def test_no_sub_binding(mycharm: type[ops.CharmBase]): ) as mgr: with pytest.raises(ops.RelationNotFoundError): # sub relations have no network - mgr.charm.model.get_binding('bar').network # type: ignore[union-attr] + mgr.charm.model.get_binding('bar').network # type: ignore def test_no_relation_error(mycharm: type[ops.CharmBase]): @@ -134,7 +134,7 @@ def test_no_relation_error(mycharm: type[ops.CharmBase]): ), ) as mgr: with pytest.raises(ops.RelationNotFoundError): - mgr.charm.model.get_binding('foo').network # type: ignore[union-attr] + mgr.charm.model.get_binding('foo').network # type: ignore def test_juju_info_network_default(mycharm: type[ops.CharmBase]): diff --git a/testing/tests/test_e2e/test_play_assertions.py b/testing/tests/test_e2e/test_play_assertions.py index 84d411a39..222aa74a9 100644 --- a/testing/tests/test_e2e/test_play_assertions.py +++ b/testing/tests/test_e2e/test_play_assertions.py @@ -33,7 +33,7 @@ def _on_event(self, event: ops.EventBase): def test_charm_heals_on_start(mycharm: type[ops.CharmBase]): def pre_event(charm: ops.CharmBase): assert charm.unit.status == ops.BlockedStatus('foo') - assert not charm.called # type: ignore[attr-defined] + assert not charm.called # type: ignore def call(charm: ops.CharmBase, _: ops.EventBase): if charm.unit.status.message == 'foo': @@ -41,9 +41,9 @@ def call(charm: ops.CharmBase, _: ops.EventBase): def post_event(charm: ops.CharmBase): assert charm.unit.status == ops.ActiveStatus('yabadoodle') - assert charm.called # type: ignore[attr-defined] + assert charm.called # type: ignore - mycharm._call = call # type: ignore[attr-defined] + mycharm._call = call # type: ignore initial_state = State(config={'foo': 'bar'}, leader=True, unit_status=BlockedStatus('foo')) @@ -75,7 +75,7 @@ def post_event(charm: ops.CharmBase): def test_relation_data_access(mycharm: type[ops.CharmBase]): - mycharm._call = lambda *_: True # type: ignore[misc] + mycharm._call = lambda *_: True # type: ignore def check_relation_data(charm: ops.CharmBase): foo_relations = charm.model.relations['relation_test'] diff --git a/testing/tests/test_e2e/test_state.py b/testing/tests/test_e2e/test_state.py index 594adca1d..74ec6d7c1 100644 --- a/testing/tests/test_e2e/test_state.py +++ b/testing/tests/test_e2e/test_state.py @@ -67,7 +67,7 @@ def define_event(cls, event_kind: str, event_type: 'type[EventBase]'): class MyCharm(CharmBase): _call: Callable[[EventBase], None] | None = None called = False - on = MyCharmEvents() # type: ignore[assignment] + on = MyCharmEvents() # type: ignore def __init__(self, framework: Framework): super().__init__(framework) @@ -122,7 +122,7 @@ def call(charm: CharmBase, e: EventBase): charm.unit.status = ActiveStatus('foo test') charm.app.status = WaitingStatus('foo barz') - mycharm._call = call # type: ignore[attr-defined] + mycharm._call = call # type: ignore out = trigger( state, 'start', @@ -233,7 +233,7 @@ def pre_event(charm: CharmBase): # with pytest.raises(Exception): # rel.data[charm.model.get_unit("remote/1")]["c"] = "d" - mycharm._call = event_handler # type: ignore[attr-defined] + mycharm._call = event_handler # type: ignore relation = Relation( endpoint='foo', interface='bar', @@ -246,7 +246,7 @@ def pre_event(charm: CharmBase): relations={relation}, ) - assert not mycharm.called # type: ignore[attr-defined] + assert not mycharm.called # type: ignore out = trigger( state, event='start', @@ -257,7 +257,7 @@ def pre_event(charm: CharmBase): }, pre_event=pre_event, ) - assert mycharm.called # type: ignore[attr-defined] + assert mycharm.called # type: ignore assert asdict(out.get_relation(relation.id)) == asdict( replace( @@ -305,12 +305,12 @@ def test_positional_arguments(klass: type[Any], num_args: tuple[int, ...]): def test_model_positional_arguments(): with pytest.raises(TypeError): - Model('', '') # type: ignore[misc] + Model('', '') # type: ignore def test_container_positional_arguments(): with pytest.raises(TypeError): - Container('', True) # type: ignore[misc] + Container('', True) # type: ignore def test_container_default_values(): @@ -539,7 +539,7 @@ def event_handler(charm: CharmBase, _: EventBase): rel.data[charm.app]['a'] = 'b' rel.data[charm.unit]['c'] = 'd' - mycharm._call = event_handler # type: ignore[attr-defined] + mycharm._call = event_handler # type: ignore relation_in = relation_type(relation_type.__name__) @@ -580,7 +580,7 @@ def event_handler(charm: CharmBase, _: EventBase): container = charm.model.unit.get_container('foo') container.add_layer(layer_name, layer, combine=True) - mycharm._call = event_handler # type: ignore[attr-defined] + mycharm._call = event_handler # type: ignore container_in = Container('foo', can_connect=True) state_in = State(containers={container_in}) @@ -604,7 +604,7 @@ def test_state_immutable_with_changed_data_ports(mycharm: type[CharmBase]): def event_handler(charm: CharmBase, _: EventBase): charm.model.unit.open_port(protocol='tcp', port=80) - mycharm._call = event_handler # type: ignore[attr-defined] + mycharm._call = event_handler # type: ignore state_in = State() state_out = trigger( @@ -623,7 +623,7 @@ def event_handler(charm: CharmBase, _: EventBase): secret = charm.model.get_secret(label='my-secret') secret.set_content({'password': 'bar'}) - mycharm._call = event_handler # type: ignore[attr-defined] + mycharm._call = event_handler # type: ignore secret_in = Secret({'password': 'foo'}, label='my-secret', owner='unit') state_in = State(secrets={secret_in}) diff --git a/testing/tests/test_e2e/test_stored_state.py b/testing/tests/test_e2e/test_stored_state.py index 7c4634715..ed4029eb5 100644 --- a/testing/tests/test_e2e/test_stored_state.py +++ b/testing/tests/test_e2e/test_stored_state.py @@ -33,7 +33,7 @@ def _on_event(self, _: ops.EventBase): def test_stored_state_default(mycharm: type[ops.CharmBase]): - out = trigger(State(), 'start', mycharm, meta=mycharm.META) # type: ignore[attr-defined] + out = trigger(State(), 'start', mycharm, meta=mycharm.META) # type: ignore assert out.get_stored_state('_stored', owner_path='MyCharm').content == { 'foo': 'bar', 'baz': {12: 142}, @@ -53,7 +53,7 @@ def test_stored_state_initialized(mycharm: type[ops.CharmBase]): ), 'start', mycharm, - meta=mycharm.META, # type: ignore[attr-defined] + meta=mycharm.META, # type: ignore ) assert out.get_stored_state('_stored', owner_path='MyCharm').content == { 'foo': 'FOOX', @@ -67,7 +67,7 @@ def test_stored_state_initialized(mycharm: type[ops.CharmBase]): def test_positional_arguments(): with pytest.raises(TypeError): - StoredState('_stored', '') # type: ignore[call-arg] + StoredState('_stored', '') # type: ignore def test_default_arguments(): From b95c0c7bbbb1082c42085630fd9f7e0457fab774 Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Tue, 16 Dec 2025 15:43:21 +1300 Subject: [PATCH 17/17] Post merge fixes. --- testing/tests/test_e2e/test_network.py | 4 ++-- testing/tests/test_e2e/test_stored_state.py | 4 ++-- testing/tests/test_e2e/test_vroot.py | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/testing/tests/test_e2e/test_network.py b/testing/tests/test_e2e/test_network.py index 7227bd2a8..b58295656 100644 --- a/testing/tests/test_e2e/test_network.py +++ b/testing/tests/test_e2e/test_network.py @@ -107,7 +107,7 @@ def test_no_sub_binding(mycharm: type[ops.CharmBase]): ) as mgr: with pytest.raises(ops.RelationNotFoundError): # sub relations have no network - mgr.charm.model.get_binding('bar').network # noqa: B018 # Used to trigger the error. + mgr.charm.model.get_binding('bar').network # type: ignore # noqa: B018 # Used to trigger the error. def test_no_relation_error(mycharm: type[ops.CharmBase]): @@ -137,7 +137,7 @@ def test_no_relation_error(mycharm: type[ops.CharmBase]): ), ) as mgr: with pytest.raises(ops.RelationNotFoundError): - mgr.charm.model.get_binding('foo').network # noqa: B018 # Used to trigger the error. + mgr.charm.model.get_binding('foo').network # type: ignore # noqa: B018 # Used to trigger the error. def test_juju_info_network_default(mycharm: type[ops.CharmBase]): diff --git a/testing/tests/test_e2e/test_stored_state.py b/testing/tests/test_e2e/test_stored_state.py index 5d89ad602..5436fd79a 100644 --- a/testing/tests/test_e2e/test_stored_state.py +++ b/testing/tests/test_e2e/test_stored_state.py @@ -19,8 +19,8 @@ class MyCharm(ops.CharmBase): META: Mapping[str, Any] = {'name': 'mycharm'} _read: ClassVar[dict[str, Any]] = {} - _stored = ops.Storedstate() - _stored2 = ops.Storedstate() + _stored = ops.StoredState() + _stored2 = ops.StoredState() def __init__(self, framework: ops.Framework): super().__init__(framework) diff --git a/testing/tests/test_e2e/test_vroot.py b/testing/tests/test_e2e/test_vroot.py index 4f6c89f51..54eecc86d 100644 --- a/testing/tests/test_e2e/test_vroot.py +++ b/testing/tests/test_e2e/test_vroot.py @@ -52,7 +52,7 @@ def test_charm_virtual_root(charm_virtual_root: Path): State(), 'start', charm_type=MyCharm, - meta=MyCharm.META, + meta=dict(MyCharm.META), charm_root=charm_virtual_root, ) assert out.unit_status == ActiveStatus('hello world') @@ -63,7 +63,7 @@ def test_charm_virtual_root_cleanup_if_exists(charm_virtual_root: Path): raw_ori_meta = yaml.safe_dump({'name': 'karl'}) meta_file.write_text(raw_ori_meta) - ctx = Context(MyCharm, meta=MyCharm.META, charm_root=charm_virtual_root) + ctx = Context(MyCharm, meta=dict(MyCharm.META), charm_root=charm_virtual_root) with ctx( ctx.on.start(), State(), @@ -84,7 +84,7 @@ def test_charm_virtual_root_cleanup_if_not_exists(charm_virtual_root: Path): assert not meta_file.exists() - ctx = Context(MyCharm, meta=MyCharm.META, charm_root=charm_virtual_root) + ctx = Context(MyCharm, meta=dict(MyCharm.META), charm_root=charm_virtual_root) with ctx( ctx.on.start(), State(),