componentsv2 0.3.1__tar.gz → 0.3.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. {componentsv2-0.3.1 → componentsv2-0.3.2}/PKG-INFO +1 -1
  2. componentsv2-0.3.2/componentsv2/__init__.py +11 -0
  3. componentsv2-0.3.2/componentsv2/component_holder.py +213 -0
  4. componentsv2-0.3.2/componentsv2/components/__init__.py +0 -0
  5. componentsv2-0.3.2/componentsv2/components/action_row.py +51 -0
  6. componentsv2-0.3.2/componentsv2/components/base.py +19 -0
  7. componentsv2-0.3.2/componentsv2/components/button.py +82 -0
  8. componentsv2-0.3.2/componentsv2/components/checkbox.py +62 -0
  9. componentsv2-0.3.2/componentsv2/components/file_upload.py +59 -0
  10. componentsv2-0.3.2/componentsv2/components/gallery.py +24 -0
  11. componentsv2-0.3.2/componentsv2/components/holders/__init__.py +0 -0
  12. componentsv2-0.3.2/componentsv2/components/holders/container.py +81 -0
  13. componentsv2-0.3.2/componentsv2/components/holders/label.py +19 -0
  14. componentsv2-0.3.2/componentsv2/components/holders/nestable.py +22 -0
  15. componentsv2-0.3.2/componentsv2/components/holders/section.py +20 -0
  16. componentsv2-0.3.2/componentsv2/components/media.py +44 -0
  17. componentsv2-0.3.2/componentsv2/components/radio_group.py +35 -0
  18. componentsv2-0.3.2/componentsv2/components/select.py +277 -0
  19. componentsv2-0.3.2/componentsv2/components/separator.py +21 -0
  20. componentsv2-0.3.2/componentsv2/components/text_display.py +16 -0
  21. componentsv2-0.3.2/componentsv2/components/text_input.py +31 -0
  22. componentsv2-0.3.2/componentsv2/utils/__init__.py +0 -0
  23. componentsv2-0.3.2/componentsv2/utils/serialize.py +8 -0
  24. componentsv2-0.3.2/componentsv2/wrapper.py +161 -0
  25. {componentsv2-0.3.1 → componentsv2-0.3.2}/componentsv2.egg-info/PKG-INFO +1 -1
  26. componentsv2-0.3.2/componentsv2.egg-info/SOURCES.txt +31 -0
  27. componentsv2-0.3.2/componentsv2.egg-info/top_level.txt +1 -0
  28. {componentsv2-0.3.1 → componentsv2-0.3.2}/pyproject.toml +1 -1
  29. componentsv2-0.3.1/componentsv2.egg-info/SOURCES.txt +0 -8
  30. componentsv2-0.3.1/componentsv2.egg-info/top_level.txt +0 -1
  31. {componentsv2-0.3.1 → componentsv2-0.3.2}/LICENSE.txt +0 -0
  32. {componentsv2-0.3.1 → componentsv2-0.3.2}/README.md +0 -0
  33. {componentsv2-0.3.1 → componentsv2-0.3.2}/componentsv2.egg-info/dependency_links.txt +0 -0
  34. {componentsv2-0.3.1 → componentsv2-0.3.2}/componentsv2.egg-info/requires.txt +0 -0
  35. {componentsv2-0.3.1 → componentsv2-0.3.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: componentsv2
3
- Version: 0.3.1
3
+ Version: 0.3.2
4
4
  License-File: LICENSE.txt
5
5
  Requires-Dist: nextcord
6
6
  Dynamic: license-file
@@ -0,0 +1,11 @@
1
+ from .components.action_row import ActionRow
2
+ from .components.base import ComponentV2
3
+ from .components.button import ButtonV2
4
+
5
+ from .components.select import (
6
+ StringSelect,
7
+ UserSelect,
8
+ RoleSelect,
9
+
10
+ DefaultValue,
11
+ )
@@ -0,0 +1,213 @@
1
+ from .components.base import ComponentV2
2
+ from .components.button import ButtonV2
3
+ from .components.action_row import ActionRow
4
+ from .components.select import SelectObject
5
+
6
+ import copy
7
+
8
+ class ComponentHolder():
9
+ def __init_subclass__(cls):
10
+ children: list[ComponentV2] = []
11
+
12
+ for base in reversed(cls.__mro__):
13
+ children.extend(
14
+ member
15
+ for member in base.__dict__.values()
16
+ if hasattr(member, "__discord_ui_model_type__")
17
+ )
18
+
19
+ if len(children) > 25:
20
+ raise TypeError("Component Holders can only contain 25 components!")
21
+
22
+ cls.children = children
23
+
24
+ def __init__(self):
25
+ self.children = copy.deepcopy(self.children)
26
+ rows: list[ActionRow] = []
27
+
28
+ for child in self.children:
29
+ if hasattr(child, "parent_holder") == False:
30
+ child.parent_holder = self
31
+
32
+ if isinstance(child, ActionRow):
33
+ rows.append(child)
34
+
35
+ self.rows = rows
36
+
37
+ for child in self.children:
38
+ if isinstance(child, ButtonV2) or isinstance(child, SelectObject): # switch StringSelect to general Select object
39
+ self.append_to_row(child.row, child)
40
+
41
+ def __rec_add_components(self, component: ComponentV2, initial: bool):
42
+ if hasattr(component, "row"):
43
+ self.append_to_row(component.row, component)
44
+
45
+ if hasattr(component, "parent_holder") == False:
46
+ component.parent_holder = self
47
+ elif isinstance(component.parent_holder, ComponentHolder):
48
+ raise TypeError("Cannot re-assign components!")
49
+
50
+ if hasattr(component, "accessory") == True:
51
+ component.accessory.__row_child__ = True
52
+ self.children.append(component.accessory)
53
+
54
+ if initial == False:
55
+ component.__row_child__ = True
56
+
57
+ self.children.append(component)
58
+
59
+ if hasattr(component, "components") == True:
60
+ for child in component.components:
61
+ self.__rec_add_components(child, False)
62
+
63
+ def add_component(self, component: ComponentV2):
64
+ self.__rec_add_components(component, True)
65
+ return self
66
+
67
+ def add_components(self, *args: ComponentV2):
68
+ components = list(args)
69
+
70
+ for component in components:
71
+ self.__rec_add_components(component, True)
72
+
73
+ def append_to_row(self, row: int, component: ComponentV2):
74
+ if row < len(self.rows):
75
+ action_row = self.rows[row]
76
+ elif row == len(self.rows):
77
+ action_row = ActionRow()
78
+ self.rows.append(action_row)
79
+ self.children.append(action_row)
80
+ else:
81
+ raise IndexError("New rows must be created sequentially (cannot skip rows).")
82
+
83
+ component.__row_child__ = True
84
+ action_row.append_component(component)
85
+
86
+ def serialize(self):
87
+ components = []
88
+
89
+ for child in self.children:
90
+ if hasattr(child, "__row_child__") == True:
91
+ continue
92
+
93
+ components.append(child.serialize())
94
+
95
+ return components
96
+
97
+ def get_component(self, custom_id: str):
98
+ for child in self.children:
99
+ if getattr(child, "custom_id", None) == custom_id:
100
+ return child
101
+
102
+ class ModalV2():
103
+ def __init__(self, title: str, custom_id: str = None):
104
+
105
+ self.custom_id = custom_id
106
+ self.title = title
107
+
108
+ self.children = []
109
+
110
+ async def __handle_component_submitted(self, sc: dict[str]):
111
+ if custom_id := sc.get("custom_id"):
112
+ component = self.get_component(custom_id)
113
+
114
+ if component is not None:
115
+ if values := sc.get("values"):
116
+ component.values = values
117
+ else:
118
+ values = None
119
+
120
+ if (value := sc.get("value")) is not None:
121
+ component.value = value
122
+ elif values:
123
+ component.value = values[0]
124
+
125
+ if component := sc.get("component"):
126
+ await self.__handle_component_submitted(component)
127
+
128
+ if components := sc.get("components"):
129
+ for child in components:
130
+ await self.__handle_component_submitted(child)
131
+
132
+ async def submitted(self, interaction, ser_components: list[dict[str]], initial: bool = True):
133
+ for sc in ser_components:
134
+ await self.__handle_component_submitted(sc)
135
+
136
+ if initial == True:
137
+ await self.on_form_submit(interaction)
138
+
139
+ def on_submit_decorator(self, func):
140
+ self.on_form_submit = func
141
+
142
+ async def on_form_submit(self, interaction):
143
+ pass
144
+
145
+ def __rec_add_components(self, component: ComponentV2, initial: bool):
146
+ if hasattr(component, "parent_holder") == False:
147
+ component.parent_holder = self
148
+ elif isinstance(component.parent_holder, ModalV2):
149
+ raise TypeError("Cannot re-assign components!")
150
+
151
+ if hasattr(component, "accessory") == True:
152
+ component.accessory.__row_child__ = True
153
+ self.children.append(component.accessory)
154
+
155
+ if initial == False:
156
+ component.__row_child__ = True
157
+
158
+ self.children.append(component)
159
+
160
+ if hasattr(component, "components") == True:
161
+ for child in component.components:
162
+ self.__rec_add_components(child, False)
163
+
164
+ if hasattr(component, "component") == True:
165
+ self.__rec_add_components(component.component, False)
166
+
167
+ def add_component(self, component: ComponentV2):
168
+ self.__rec_add_components(component, True)
169
+ return self
170
+
171
+ def add_components(self, *args: ComponentV2):
172
+ components = list(args)
173
+
174
+ for component in components:
175
+ self.__rec_add_components(component, True)
176
+
177
+ def serialize(self):
178
+ components = []
179
+
180
+ for child in self.children:
181
+ if hasattr(child, "__row_child__") == True:
182
+ continue
183
+
184
+ components.append(child.serialize())
185
+
186
+ return {
187
+ "type": 9,
188
+ "data": {
189
+ "custom_id": self.custom_id,
190
+ "title": self.title,
191
+ "components": components
192
+ }
193
+ }
194
+
195
+ def get_component(self, custom_id: str):
196
+ if custom_id == None:
197
+ return
198
+
199
+ for child in self.children:
200
+ if getattr(child, "custom_id", None) == custom_id:
201
+ return child
202
+
203
+ def get_components(self) -> list[ComponentV2]:
204
+ components = []
205
+
206
+ for child in self.children:
207
+ if hasattr(child, "__row_child__") == True:
208
+ continue
209
+
210
+ components.append(child)
211
+
212
+ return components
213
+
File without changes
@@ -0,0 +1,51 @@
1
+ from .base import ComponentV2
2
+ from typing import Optional
3
+
4
+ class ActionRow(ComponentV2):
5
+ types_to_max = {
6
+ 2: 5,
7
+
8
+ 3: 1,
9
+ 5: 1,
10
+ 6: 1,
11
+ 7: 1,
12
+ 8: 1,
13
+ }
14
+
15
+ def __init__(self, components: list[ComponentV2] = [], id: Optional[int] = None):
16
+ super().__init__(1, id)
17
+
18
+ self.component_type: Optional[int] = None
19
+ self.components: list[ComponentV2] = components
20
+
21
+ self.__container_compatible__ = True
22
+
23
+ def serialize(self) -> dict:
24
+ base_dict: dict = super(ActionRow, self).serialize()
25
+ component_array = []
26
+
27
+ for component in self.components:
28
+ component_array.append(component.serialize())
29
+
30
+ base_dict["components"] = component_array
31
+ return base_dict
32
+
33
+ def append_component(self, component: ComponentV2):
34
+ if self.component_type != None and component.type != self.component_type:
35
+ raise TypeError("Cannot assign multiple different types to one action row.")
36
+
37
+ max = self.types_to_max[component.type]
38
+ if max == None:
39
+ raise TypeError(f"Component type {component.type} not accepted in an action row!")
40
+
41
+ if len(self.components) >= max:
42
+ raise ValueError(f"Cannot add more components to this action row! Max: {max}")
43
+
44
+ self.components.append(component)
45
+ return self
46
+
47
+ def append_components(self, *args: ComponentV2):
48
+ components = list(args)
49
+
50
+ for component in components:
51
+ self.append_component(component)
@@ -0,0 +1,19 @@
1
+ from typing import Optional
2
+
3
+ class ComponentV2():
4
+ def __init__(self, type: int, id: Optional[int] = None):
5
+ self.type = type
6
+ self.id = id
7
+
8
+ self.value = None
9
+ self.values = None
10
+
11
+ @property
12
+ def __discord_ui_model_type__(self):
13
+ return True
14
+
15
+ def serialize(self) -> dict:
16
+ return {
17
+ "type": self.type,
18
+ "id": self.id,
19
+ }
@@ -0,0 +1,82 @@
1
+ import nextcord
2
+
3
+ from typing import Optional
4
+ from .base import ComponentV2
5
+
6
+ from ..utils.serialize import serialize_emoji
7
+
8
+ class ButtonV2(ComponentV2):
9
+ def __init__(self, style: nextcord.ButtonStyle, label: Optional[str] = None, custom_id: Optional[str] = None, url: Optional[str] = None, disabled: Optional[bool] = False, emoji: Optional[nextcord.PartialEmoji] = None, row: Optional[int] = 1, id: Optional[int] = None):
10
+ if style != nextcord.ButtonStyle.link and custom_id == None:
11
+ raise TypeError("Non-link buttons must have a custom_id attribute!")
12
+
13
+ if style == nextcord.ButtonStyle.link and (custom_id != None or url == None):
14
+ raise TypeError("Link buttons cannot have a custom_id and must have a url attribute!")
15
+
16
+ super().__init__(2, id)
17
+
18
+ self.row = row
19
+
20
+ self.label = label
21
+ self.emoji = emoji
22
+
23
+ self.custom_id = custom_id
24
+ self.url = url
25
+
26
+ self.style = style.value
27
+
28
+ self.disabled = disabled
29
+
30
+ self.callback = None
31
+ self.registered = False
32
+
33
+ self.__has_holder = None
34
+
35
+ def __call__(self, func):
36
+ if self.registered == True:
37
+ raise ValueError("Buttons cannot be registered to multiple callbacks!")
38
+
39
+ self.callback = func
40
+ self.registered = True
41
+
42
+ return self
43
+
44
+ def on_callback(self, func):
45
+ if self.registered == True:
46
+ raise ValueError("Buttons cannot be registered to multiple callbacks!")
47
+
48
+ self.callback = func
49
+ self.registered = True
50
+
51
+ self.__has_holder = False
52
+
53
+ return self
54
+
55
+ def serialize(self):
56
+ base_dict: dict = super(ButtonV2, self).serialize()
57
+
58
+ base_dict["label"] = self.label
59
+
60
+ base_dict["custom_id"] = self.custom_id
61
+ base_dict["url"] = self.url
62
+
63
+ base_dict["style"] = self.style
64
+
65
+ base_dict["disabled"] = self.disabled
66
+
67
+ if self.emoji != None:
68
+ base_dict["emoji"] = serialize_emoji(self.emoji)
69
+
70
+ return base_dict
71
+
72
+ async def activated(self, interaction):
73
+ if self.disabled == True:
74
+ return
75
+
76
+ if self.registered == False:
77
+ raise ValueError("Attempted to activate button, but it is not registered to a callback!")
78
+
79
+ if self.__has_holder != False:
80
+ await self.callback(self.parent_holder, self, interaction)
81
+ else:
82
+ await self.callback(interaction)
@@ -0,0 +1,62 @@
1
+ from .base import ComponentV2
2
+
3
+ class Checkbox(ComponentV2):
4
+ def __init__(self, custom_id: str, default: bool = None, id = None):
5
+ super().__init__(23, id)
6
+
7
+ self.custom_id = custom_id
8
+ self.default = default
9
+
10
+ def serialize(self):
11
+ base_dict = super().serialize()
12
+
13
+ base_dict["custom_id"] = self.custom_id
14
+ base_dict["default"] = self.default
15
+
16
+ return base_dict
17
+
18
+ class CheckboxGroup(ComponentV2):
19
+ class Option():
20
+ def __init__(self, value: str, label: str, description: str = None, default: bool = None):
21
+ self.value = value
22
+ self.label = label
23
+
24
+ self.description = description
25
+ self.default = default
26
+
27
+ def serialize(self):
28
+ return {
29
+ "value": self.value,
30
+ "label": self.label,
31
+
32
+ "description": self.description,
33
+ "default": self.default,
34
+ }
35
+
36
+ def __init__(self, custom_id: str, options: list[Option], min_values: int = None, max_values: int = None, required: bool = None, id = None):
37
+ super().__init__(22, id)
38
+
39
+ if required == True and min_values and min_values < 1:
40
+ raise ValueError("min_values must be ommited or >= 1 when required is omitted or True!")
41
+
42
+ self.custom_id = custom_id
43
+ self.options = options
44
+ self.min_values = min_values
45
+ self.max_values = max_values
46
+ self.required = required
47
+
48
+ def serialize(self):
49
+ base_dict = super().serialize()
50
+
51
+ checkbox_options = []
52
+
53
+ for option in self.options:
54
+ checkbox_options.append(option.serialize())
55
+
56
+ base_dict["custom_id"] = self.custom_id
57
+ base_dict["options"] = checkbox_options
58
+ base_dict["min_values"] = self.min_values
59
+ base_dict["max_values"] = self.max_values
60
+ base_dict["required"] = self.required
61
+
62
+ return base_dict
@@ -0,0 +1,59 @@
1
+ import nextcord
2
+ from nextcord import Interaction
3
+
4
+ from .base import ComponentV2
5
+ from .gallery import MediaGallery as Gallery
6
+ from .media import MediaItem
7
+
8
+ def get_files(self, interaction, files: list[str]) -> list[MediaItem]: ## works for all files
9
+ resolved = interaction.data.get("resolved")
10
+ attachments: dict[str] = resolved.get("attachments")
11
+
12
+ links = []
13
+ for id in files:
14
+ data = attachments.get(id)
15
+ if data:
16
+ links.append(data.get("url"))
17
+
18
+ media = []
19
+
20
+ for link in links:
21
+ item = MediaItem(
22
+ nextcord.UnfurledMedia(link)
23
+ )
24
+
25
+ media.append(item)
26
+
27
+ return media
28
+
29
+ def get_gallery(self, interaction: Interaction, files: list[str]) -> Gallery: ## this only works for images
30
+ media = get_files(self, interaction, files)
31
+ return Gallery(media)
32
+
33
+ class FileUpload(ComponentV2):
34
+ def __init__(self, custom_id: str, min_values: int = None, max_values: int = None, required: bool = True, id = None):
35
+ if min_values and (min_values < 0 or min_values > 10):
36
+ raise ValueError("min_values must be between 0-10.")
37
+
38
+ if max_values and (max_values < 1 or max_values > 10):
39
+ raise ValueError("max_values must be between 1-10.")
40
+
41
+ if required == True and min_values and min_values < 1:
42
+ raise ValueError("min_values must be ommited or >= 1 when required is omitted or True!")
43
+
44
+ super().__init__(19, id)
45
+
46
+ self.custom_id = custom_id
47
+ self.min_values = min_values
48
+ self.max_values = max_values
49
+ self.required = required
50
+
51
+ def serialize(self):
52
+ base_dict = super().serialize()
53
+
54
+ base_dict["min_values"] = self.min_values
55
+ base_dict["max_values"] = self.max_values
56
+ base_dict["custom_id"] = self.custom_id
57
+ base_dict["required"] = self.required
58
+
59
+ return base_dict
@@ -0,0 +1,24 @@
1
+ from nextcord import UnfurledMedia
2
+ from .base import ComponentV2
3
+ from.media import MediaItem
4
+
5
+ class MediaGallery(ComponentV2):
6
+ def __init__(self, items: list[MediaItem], id = None):
7
+ if len(items) < 1 or len(items) > 10:
8
+ raise ValueError("List of Media Items must be within the range 1-10.")
9
+
10
+ super().__init__(12, id)
11
+ self.items = items
12
+
13
+ self.__container_compatible__ = True
14
+
15
+ def serialize(self):
16
+ base_dict = super().serialize()
17
+
18
+ items = []
19
+ for media in self.items:
20
+ items.append(media.serialize())
21
+
22
+ base_dict["items"] = items
23
+
24
+ return base_dict
@@ -0,0 +1,81 @@
1
+ from .nestable import Nestable
2
+ from ..base import ComponentV2
3
+
4
+ from ..action_row import ActionRow
5
+ from ..text_display import TextDisplay
6
+
7
+ from typing import Optional
8
+
9
+ class Container(Nestable):
10
+ def __init__(self, components: list[ComponentV2] = None, accent_color: int = None, spoiler: bool = None, id: Optional[int] = None):
11
+ if components is None:
12
+ components = []
13
+
14
+ for component in components:
15
+ if hasattr(component, "__container_compatible__") == False:
16
+ raise TypeError(f"Component {component.__class__.__name__} not accepted in Containers!")
17
+
18
+ super().__init__(17, id, components)
19
+
20
+ self.accent_color = accent_color
21
+ self.spoiler = spoiler
22
+
23
+ self.footer = None
24
+
25
+ def get_row(self, row: int) -> tuple[int, ActionRow]:
26
+ total_rows = -1
27
+
28
+ for component in self.components:
29
+ if isinstance(component, ActionRow) and total_rows == row:
30
+ return total_rows, component
31
+ elif isinstance(component, ActionRow):
32
+ total_rows += 1
33
+
34
+ return total_rows, None
35
+
36
+ def append_component(self, component: ComponentV2): # chained
37
+ if hasattr(component, "row"):
38
+ row, action_row = self.get_row(component.row)
39
+
40
+ if action_row != None:
41
+ action_row.append_component(component)
42
+ return self
43
+ elif row + 1 == component.row:
44
+ action_row = ActionRow()
45
+ action_row.append_component(component)
46
+
47
+ return self.append_component(action_row)
48
+ else:
49
+ raise IndexError("Action rows must be created sequentially!")
50
+
51
+ self.components.append(component)
52
+
53
+ if self.footer is not None:
54
+ self.components.remove(self.footer)
55
+ self.components.append(self.footer)
56
+
57
+ return self
58
+
59
+ def add_field(self, title: str, content: str):
60
+ return self.append_component(
61
+ TextDisplay(
62
+ f"**{title}**\n{content}"
63
+ )
64
+ )
65
+
66
+ def set_footer(self, footer: str):
67
+ if self.footer is not None:
68
+ self.components.remove(self.footer)
69
+
70
+ self.footer = TextDisplay(f"-# {footer}")
71
+ self.components.append(
72
+ self.footer
73
+ )
74
+
75
+ def serialize(self):
76
+ base_dict = super().serialize()
77
+
78
+ base_dict["accent_color"] = self.accent_color
79
+ base_dict["spoiler"] = self.spoiler
80
+
81
+ return base_dict
@@ -0,0 +1,19 @@
1
+ from ..base import ComponentV2
2
+
3
+ class Label(ComponentV2):
4
+ def __init__(self, label: str, component: ComponentV2, description: str = None, id: int = None):
5
+ super().__init__(18, id)
6
+
7
+ self.label = label
8
+
9
+ self.component = component
10
+ self.description = description
11
+
12
+ def serialize(self):
13
+ base_dict = super().serialize()
14
+
15
+ base_dict["label"] = self.label
16
+ base_dict["component"] = self.component.serialize()
17
+ base_dict["description"] = self.description
18
+
19
+ return base_dict
@@ -0,0 +1,22 @@
1
+ from ..base import ComponentV2
2
+ from typing import Optional
3
+
4
+ class Nestable(ComponentV2): ## a 'Nestable' base class is able to nest other components inside of itself. Action rows are intentionally left out of this class, as it exclusive for ComponentsV2.
5
+ def __init__(self, type: int, id: Optional[int] = None, components: list[ComponentV2] = []):
6
+ super().__init__(type, id)
7
+
8
+ self.components = components
9
+
10
+ def to_component_list(self):
11
+ components = []
12
+
13
+ for component in self.components:
14
+ components.append(component.serialize())
15
+
16
+ return components
17
+
18
+ def serialize(self):
19
+ base_dict = super().serialize()
20
+ base_dict["components"] = self.to_component_list()
21
+
22
+ return base_dict
@@ -0,0 +1,20 @@
1
+ from .nestable import Nestable
2
+ from ..base import ComponentV2
3
+
4
+ from typing import Optional
5
+
6
+ class Section(Nestable):
7
+ def __init__(self, components: list[ComponentV2], accessory: ComponentV2, id: Optional[int] = None):
8
+ if len(components) < 1 or len(components) > 3:
9
+ raise ValueError("Section can only take 1-3 components and cannot have an empty components table!")
10
+
11
+ super().__init__(9, id, components)
12
+ self.accessory = accessory
13
+
14
+ self.__container_compatible__ = True
15
+
16
+ def serialize(self):
17
+ base_dict = super().serialize()
18
+ base_dict["accessory"] = self.accessory.serialize()
19
+
20
+ return base_dict