Passed
Pull Request — main (#389)
by
unknown
02:23
created

pincer.cog.Cog.__init__()   A

Complexity

Conditions 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 3
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nop 2
1
# Copyright Pincer 2021-Present
0 ignored issues
show
introduced by
Missing module docstring
Loading history...
2
# Full MIT License can be found in `LICENSE` at the project root.
3
4
from __future__ import annotations
5
from asyncio import ensure_future
6
7
from importlib import reload, import_module
8
from types import ModuleType
9
from typing import TYPE_CHECKING, List
10
11
from . import client as _client
12
from .commands.chat_command_handler import ChatCommandHandler
13
from .commands.interactable import Interactable
14
from .exceptions import CogAlreadyExists, CogNotFound
15
16
if TYPE_CHECKING:
17
    from typing import Type
18
    from .client import Client
19
20
21
def get_cog_name(cog: Type[Cog]) -> str:
22
    """Gets the path to import a cog. This is used as a unique identifier"""
23
    try:
24
        return f"{cog.__module__}.{cog.__name__}"
25
    except AttributeError:
26
        return f"{cog.__module__}.{cog.__class__.__name__}"
27
28
29
class CogManager:
30
    """
31
    A class that can load and unload cogs
32
    """
33
34
    def load_cog(self, cog: Type[Cog]):
35
        """Load a cog from a string path, setup method in COG may
36
        optionally have a first argument which will contain the client!
37
38
        :Example usage:
39
40
        run.py
41
42
        .. code-block:: python3
43
44
             from pincer import Client
45
             from cogs.say import SayCommand
46
47
             class MyClient(Client):
48
                 def __init__(self, *args, **kwargs):
49
                     self.load_cog(SayCommand)
50
                     super().__init__(*args, **kwargs)
51
52
        cogs/say.py
53
54
        .. code-block:: python3
55
56
             from pincer import command
57
58
             class SayCommand(Cog):
59
                 @command()
60
                 async def say(self, message: str) -> str:
61
                     return message
62
63
        Parameters
64
        ----------
65
        cog : Type[:class:`~pincer.cog.Cog`]
66
            The cog to load.
67
        """
68
        if cog in ChatCommandHandler.managers:
69
            raise CogAlreadyExists(
70
                f"Cog `{cog}` is trying to be loaded but already exists."
71
            )
72
73
        cog_manager = cog(self)
74
75
        ChatCommandHandler.managers.append(cog_manager)
76
77
    def load_cogs(self, *cogs: Type[Cog]):
78
        """
79
        Loads a list of cogs
80
81
        Parameters
82
        ----------
83
        *cogs : Type[:class:`~pincer.cog.Cog`]
84
            A list of cogs to load.
85
        """
86
        for cog in cogs:
87
            self.load_cog(cog)
88
89
    def load_module(self, module: ModuleType):
90
        """Loads the cogs from a module recursively.
91
92
        Parameters
93
        ----------
94
        module : :class:`~types.ModuleType`
95
            The module to load.
96
        """
97
        for item in module.__dict__.values():
98
            if isinstance(item, ModuleType):
99
                self.load_module(item)
100
            elif Cog in getattr(item, "__bases__", []):
101
                self.load_cog(item)
102
103
    def reload_cog(self, cog: Type[Cog]):
104
        """Reloads a cog.
105
106
        Parameters
107
        ----------
108
        cog : Type[:class:`~pincer.cog.Cog`]
109
            The cog to load.
110
        """
111
112
        # Remove application commands registered to this cog
113
        for item in ChatCommandHandler.managers:
114
            if get_cog_name(item) == get_cog_name(cog):
115
                old_cog = item
116
                break
117
        else:
118
            raise CogNotFound(f"Cog `{cog}` could not be found!")
119
120
        to_pop = []
121
122
        for key, command in ChatCommandHandler.register.items():
123
            if not command:
124
                continue
125
            if command.manager == old_cog:
126
                to_pop.append(key)
127
128
        for pop in to_pop:
129
            ChatCommandHandler.register.pop(pop)
130
131
        ChatCommandHandler.managers.remove(old_cog)
132
133
        # Remove events registered to this cog
134
        for event in type(old_cog).__dict__.values():
135
            if isinstance(event, _client.PartialEvent):
136
                _client._events.pop(event.func.__name__)
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like _events was declared protected and should not be accessed from this context.

Prefixing a member variable _ is usually regarded as the equivalent of declaring it with protected visibility that exists in other languages. Consequentially, such a member should only be accessed from the same class or a child class:

class MyParent:
    def __init__(self):
        self._x = 1;
        self.y = 2;

class MyChild(MyParent):
    def some_method(self):
        return self._x    # Ok, since accessed from a child class

class AnotherClass:
    def some_method(self, instance_of_my_child):
        return instance_of_my_child._x   # Would be flagged as AnotherClass is not
                                         # a child class of MyParent
Loading history...
137
138
        mod = reload(import_module(cog.__module__))
139
        new_cog = getattr(mod, cog.__name__)
140
        self.load_cog(new_cog)
141
        ChatCommandHandler.has_been_initialized = False
142
        ensure_future(ChatCommandHandler().initialize())
0 ignored issues
show
Bug introduced by
It seems like a value for argument client is missing in the constructor call.
Loading history...
143
144
    @staticmethod
145
    def get_cogs() -> List[Cog]:
146
        """Get a dictionary of all loaded cogs.
147
148
        The key/value pair is import path/cog class.
149
150
        Returns
151
        -------
152
        List[:class:`~pincer.cog.Cog`]
153
            The list of cogs
154
        """
155
        return [
156
            manager for manager in ChatCommandHandler.managers
157
            if isinstance(manager, Cog)
158
        ]
159
160
161
class Cog(Interactable):
162
    """A cog object
163
    This is an object that can register commands and message components that isn't a
164
    client. It also can be loaded and unloaded in runtime so commands can be changed
165
    without restarting the bot.
166
    """
167
168
    def __init__(self, client: Client) -> None:
169
        self.client = client
170
171
        super().__init__()
172