diff --git a/pristan/components/plugins_group.py b/pristan/components/plugins_group.py index 4f0989d..2820bd6 100644 --- a/pristan/components/plugins_group.py +++ b/pristan/components/plugins_group.py @@ -109,7 +109,7 @@ def _pop_group(self, requested_name: str) -> List[Plugin[PluginResult]]: raise KeyError(requested_name) removed_plugins = self.plugins_by_requested_names.pop(requested_name) - self.plugins = [plugin for plugin in self.plugins if plugin.requested_name != requested_name] + self.plugins[:] = [plugin for plugin in self.plugins if plugin.requested_name != requested_name] return removed_plugins def _pop_exact_plugin(self, key: str) -> List[Plugin[PluginResult]]: diff --git a/pyproject.toml b/pyproject.toml index a42c1fb..7ca3ab0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "pristan" -version = "0.0.14" +version = "0.0.15" authors = [{ name = "Evgeniy Blinov", email = "zheni-b@yandex.ru" }] description = "Function-based plugin system with respect to typing" readme = "README.md" diff --git a/tests/units/components/test_plugins_group.py b/tests/units/components/test_plugins_group.py index 32bbc6f..a3b659b 100644 --- a/tests/units/components/test_plugins_group.py +++ b/tests/units/components/test_plugins_group.py @@ -248,10 +248,14 @@ def test_getitem_good_key(): def test_pop_by_base_name(group_with_named_duplicates): group, plugins = group_with_named_duplicates + plugins_reference = group.plugins + removed_plugins = group.pop('name') assert removed_plugins == plugins[:3] + assert group.plugins is plugins_reference assert group.plugins == [plugins[3]] + assert plugins_reference == [plugins[3]] assert group.plugins_by_requested_names == { 'name2': [plugins[3]], } @@ -259,10 +263,14 @@ def test_pop_by_base_name(group_with_named_duplicates): def test_pop_first_plugin_by_name_1(group_with_named_duplicates): group, plugins = group_with_named_duplicates + plugins_reference = group.plugins + removed_plugins = group.pop('name-1') assert removed_plugins == [plugins[0]] + assert group.plugins is plugins_reference assert [x.name for x in group.plugins] == ['name', 'name-2', 'name2'] + assert [x.name for x in plugins_reference] == ['name', 'name-2', 'name2'] assert group.plugins_by_requested_names == { 'name': [plugins[1], plugins[2]], 'name2': [plugins[3]], @@ -271,10 +279,14 @@ def test_pop_first_plugin_by_name_1(group_with_named_duplicates): def test_pop_middle_plugin_renumbers_remaining_duplicates(group_with_named_duplicates): group, plugins = group_with_named_duplicates + plugins_reference = group.plugins + removed_plugins = group.pop('name-2') assert removed_plugins == [plugins[1]] + assert group.plugins is plugins_reference assert [x.name for x in group.plugins] == ['name', 'name-2', 'name2'] + assert [x.name for x in plugins_reference] == ['name', 'name-2', 'name2'] assert group.plugins_by_requested_names == { 'name': [plugins[0], plugins[2]], 'name2': [plugins[3]], @@ -283,10 +295,14 @@ def test_pop_middle_plugin_renumbers_remaining_duplicates(group_with_named_dupli def test_pop_last_plugin_keeps_compact_numbering(group_with_named_duplicates): group, plugins = group_with_named_duplicates + plugins_reference = group.plugins + removed_plugins = group.pop('name-3') assert removed_plugins == [plugins[2]] + assert group.plugins is plugins_reference assert [x.name for x in group.plugins] == ['name', 'name-2', 'name2'] + assert [x.name for x in plugins_reference] == ['name', 'name-2', 'name2'] assert group.plugins_by_requested_names == { 'name': [plugins[0], plugins[1]], 'name2': [plugins[3]], @@ -295,11 +311,14 @@ def test_pop_last_plugin_keeps_compact_numbering(group_with_named_duplicates): def test_pop_only_plugin_by_name_1_removes_requested_name_bucket(group_with_named_duplicates): group, plugins = group_with_named_duplicates + plugins_reference = group.plugins removed_plugins = group.pop('name2-1') assert removed_plugins == [plugins[3]] + assert group.plugins is plugins_reference assert group.plugins == plugins[:3] + assert plugins_reference == plugins[:3] assert group.plugins_by_requested_names == { 'name': plugins[:3], } diff --git a/tests/units/decorators/test_slot.py b/tests/units/decorators/test_slot.py index 210265b..7e29809 100644 --- a/tests/units/decorators/test_slot.py +++ b/tests/units/decorators/test_slot.py @@ -1081,21 +1081,23 @@ def plugin(): # noqa: F811 def test_delitem_removes_plugins_from_slot(folder_slot, folder_plugin): + bread_crumbs = [] + @folder_slot(slot) def some_slot(): - ... + bread_crumbs.append('slot') @folder_plugin(some_slot) def plugin(): - ... + bread_crumbs.append('plugin_1') @folder_plugin(some_slot) def plugin(): # noqa: F811 - ... + bread_crumbs.append('plugin_2') @folder_plugin(some_slot) def plugin2(): - ... + bread_crumbs.append('plugin2') del some_slot['plugin'] @@ -1107,6 +1109,10 @@ def plugin2(): assert len(some_slot) == 1 assert 'plugin' not in some_slot + some_slot() + + assert bread_crumbs == ['plugin2'] + def test_pop_removes_plugin_and_returns_detached_selection(folder_slot): bread_crumbs = [] @@ -1137,6 +1143,194 @@ def plugin_3(a): assert bread_crumbs == ['plugin_2_1'] + bread_crumbs.clear() + + some_slot(1) + + assert bread_crumbs == ['plugin_1_1', 'plugin_3_1'] + + +def test_delitem_by_base_name_last_list_plugin_falls_back_to_slot_body(folder_slot, subscribable_list_type): + body_calls = [] + + @folder_slot(slot) + def some_slot(a) -> subscribable_list_type[int]: + body_calls.append(a) + return [] + + @some_slot.plugin('plugin') + def plugin_1(a): + return a + + assert some_slot(1) == [1] + assert body_calls == [] + + del some_slot['plugin'] + + assert some_slot.keys() == () + assert len(some_slot) == 0 + assert 'plugin' not in some_slot + assert 'plugin-1' not in some_slot + assert [x.name for x in some_slot] == [] + assert some_slot(1) == [] + assert body_calls == [1] + + +def test_pop_by_base_name_last_list_plugin_falls_back_to_slot_body(folder_slot, subscribable_list_type): + body_calls = [] + + @folder_slot(slot) + def some_slot(a) -> subscribable_list_type[int]: + body_calls.append(a) + return [] + + @some_slot.plugin('plugin') + def plugin_1(a): + return a + + assert some_slot(1) == [1] + assert body_calls == [] + + removed_plugins = some_slot.pop('plugin') + + assert [x.name for x in removed_plugins] == ['plugin'] + assert removed_plugins(2) == [2] + assert some_slot.keys() == () + assert len(some_slot) == 0 + assert 'plugin' not in some_slot + assert 'plugin-1' not in some_slot + assert [x.name for x in some_slot] == [] + assert some_slot(2) == [] + assert body_calls == [2] + + +def test_delitem_by_base_name_removes_group_from_list_slot_call(folder_slot, subscribable_list_type): + @folder_slot(slot) + def some_slot(a) -> subscribable_list_type[int]: # noqa: ARG001 + return [] + + @some_slot.plugin('plugin') + def plugin_1(a): + return a + + @some_slot.plugin('plugin') + def plugin_2(a): + return a + 1 + + @some_slot.plugin('other') + def other(a): + return a + 2 + + assert some_slot(1) == [1, 2, 3] + + del some_slot['plugin'] + + assert some_slot.keys() == ('other',) + assert len(some_slot) == 1 + assert 'plugin' not in some_slot + assert 'plugin-1' not in some_slot + assert 'plugin-2' not in some_slot + assert 'other' in some_slot + assert [x.name for x in some_slot] == ['other'] + assert some_slot(1) == [3] + + +def test_pop_by_base_name_returns_detached_group_and_keeps_survivors_in_list_slot_call(folder_slot, subscribable_list_type): + @folder_slot(slot) + def some_slot(a) -> subscribable_list_type[int]: # noqa: ARG001 + return [] + + @some_slot.plugin('plugin') + def plugin_1(a): + return a + + @some_slot.plugin('plugin') + def plugin_2(a): + return a + 1 + + @some_slot.plugin('other') + def other(a): + return a + 2 + + assert some_slot(1) == [1, 2, 3] + + removed_plugins = some_slot.pop('plugin') + + assert [x.name for x in removed_plugins] == ['plugin', 'plugin-2'] + assert removed_plugins(1) == [1, 2] + assert some_slot.keys() == ('other',) + assert len(some_slot) == 1 + assert 'plugin' not in some_slot + assert 'plugin-1' not in some_slot + assert 'plugin-2' not in some_slot + assert 'other' in some_slot + assert [x.name for x in some_slot] == ['other'] + assert some_slot(1) == [3] + + +def test_delitem_by_base_name_removes_group_from_dict_slot_call(folder_slot, subscribable_dict_type): + @folder_slot(slot) + def some_slot(a) -> subscribable_dict_type[str, int]: # noqa: ARG001 + return {} + + @some_slot.plugin('plugin') + def plugin_1(a): + return a + + @some_slot.plugin('plugin') + def plugin_2(a): + return a + 1 + + @some_slot.plugin('other') + def other(a): + return a + 2 + + assert some_slot(1) == {'plugin': 1, 'plugin-2': 2, 'other': 3} + + del some_slot['plugin'] + + assert some_slot.keys() == ('other',) + assert len(some_slot) == 1 + assert 'plugin' not in some_slot + assert 'plugin-1' not in some_slot + assert 'plugin-2' not in some_slot + assert 'other' in some_slot + assert [x.name for x in some_slot] == ['other'] + assert some_slot(1) == {'other': 3} + + +def test_pop_by_base_name_returns_detached_group_and_keeps_survivors_in_dict_slot_call(folder_slot, subscribable_dict_type): + @folder_slot(slot) + def some_slot(a) -> subscribable_dict_type[str, int]: # noqa: ARG001 + return {} + + @some_slot.plugin('plugin') + def plugin_1(a): + return a + + @some_slot.plugin('plugin') + def plugin_2(a): + return a + 1 + + @some_slot.plugin('other') + def other(a): + return a + 2 + + assert some_slot(1) == {'plugin': 1, 'plugin-2': 2, 'other': 3} + + removed_plugins = some_slot.pop('plugin') + + assert [x.name for x in removed_plugins] == ['plugin', 'plugin-2'] + assert removed_plugins(1) == {'plugin': 1, 'plugin-2': 2} + assert some_slot.keys() == ('other',) + assert len(some_slot) == 1 + assert 'plugin' not in some_slot + assert 'plugin-1' not in some_slot + assert 'plugin-2' not in some_slot + assert 'other' in some_slot + assert [x.name for x in some_slot] == ['other'] + assert some_slot(1) == {'other': 3} + def test_pop_returns_default_for_missing_key(folder_slot): @folder_slot(slot) @@ -1199,31 +1393,49 @@ def plugin_3(a, b=6): def test_delitem_and_pop_support_exact_duplicate_keys(folder_slot): + bread_crumbs = [] + @folder_slot(slot) def some_slot(): ... @some_slot.plugin('plugin') def plugin_1(): - ... + bread_crumbs.append('plugin_1') @some_slot.plugin('plugin') def plugin_2(): - ... + bread_crumbs.append('plugin_2') @some_slot.plugin('plugin') def plugin_3(): - ... + bread_crumbs.append('plugin_3') del some_slot['plugin-1'] assert [x.name for x in some_slot.plugins.plugins] == ['plugin', 'plugin-2'] + some_slot() + + assert bread_crumbs == ['plugin_2', 'plugin_3'] + + bread_crumbs.clear() + removed_plugins = some_slot.pop('plugin-2') assert [x.name for x in removed_plugins] == ['plugin-2'] assert [x.name for x in some_slot.plugins.plugins] == ['plugin'] + removed_plugins() + + assert bread_crumbs == ['plugin_3'] + + bread_crumbs.clear() + + some_slot() + + assert bread_crumbs == ['plugin_2'] + def test_delitem_with_name_1_removes_first_plugin(folder_slot): bread_crumbs = []