scurrypy 0.5__tar.gz → 0.5.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.

Potentially problematic release.


This version of scurrypy might be problematic. Click here for more details.

Files changed (61) hide show
  1. {scurrypy-0.5 → scurrypy-0.5.2}/PKG-INFO +6 -5
  2. {scurrypy-0.5 → scurrypy-0.5.2}/README.md +5 -4
  3. {scurrypy-0.5 → scurrypy-0.5.2}/pyproject.toml +7 -7
  4. {scurrypy-0.5/discord → scurrypy-0.5.2/scurrypy}/__init__.py +92 -92
  5. {scurrypy-0.5/discord → scurrypy-0.5.2/scurrypy}/client_like.py +1 -1
  6. {scurrypy-0.5/discord → scurrypy-0.5.2/scurrypy}/dispatch/command_dispatcher.py +3 -3
  7. {scurrypy-0.5/discord → scurrypy-0.5.2/scurrypy}/dispatch/event_dispatcher.py +15 -15
  8. {scurrypy-0.5/discord → scurrypy-0.5.2/scurrypy}/events/interaction_events.py +2 -2
  9. {scurrypy-0.5/discord → scurrypy-0.5.2/scurrypy}/events/message_events.py +4 -4
  10. {scurrypy-0.5/discord → scurrypy-0.5.2/scurrypy}/intents.py +1 -1
  11. scurrypy-0.5.2/scurrypy/model.py +71 -0
  12. {scurrypy-0.5/discord → scurrypy-0.5.2/scurrypy}/parts/channel.py +1 -1
  13. {scurrypy-0.5/discord → scurrypy-0.5.2/scurrypy}/parts/command.py +4 -4
  14. {scurrypy-0.5/discord → scurrypy-0.5.2/scurrypy}/parts/components.py +12 -12
  15. {scurrypy-0.5/discord → scurrypy-0.5.2/scurrypy}/parts/components_v2.py +10 -10
  16. {scurrypy-0.5/discord → scurrypy-0.5.2/scurrypy}/parts/message.py +4 -4
  17. {scurrypy-0.5/discord → scurrypy-0.5.2/scurrypy}/resources/channel.py +2 -2
  18. {scurrypy-0.5/discord → scurrypy-0.5.2/scurrypy}/resources/guild.py +3 -3
  19. {scurrypy-0.5/discord → scurrypy-0.5.2/scurrypy}/resources/interaction.py +4 -4
  20. {scurrypy-0.5/discord → scurrypy-0.5.2/scurrypy}/resources/message.py +3 -3
  21. {scurrypy-0.5 → scurrypy-0.5.2}/scurrypy.egg-info/PKG-INFO +6 -5
  22. scurrypy-0.5.2/scurrypy.egg-info/SOURCES.txt +56 -0
  23. scurrypy-0.5.2/scurrypy.egg-info/top_level.txt +1 -0
  24. scurrypy-0.5/discord/model.py +0 -90
  25. scurrypy-0.5/scurrypy.egg-info/SOURCES.txt +0 -56
  26. scurrypy-0.5/scurrypy.egg-info/top_level.txt +0 -1
  27. {scurrypy-0.5 → scurrypy-0.5.2}/LICENSE +0 -0
  28. {scurrypy-0.5/discord → scurrypy-0.5.2/scurrypy}/client.py +0 -0
  29. {scurrypy-0.5/discord → scurrypy-0.5.2/scurrypy}/config.py +0 -0
  30. {scurrypy-0.5/discord → scurrypy-0.5.2/scurrypy}/dispatch/__init__.py +0 -0
  31. {scurrypy-0.5/discord → scurrypy-0.5.2/scurrypy}/dispatch/prefix_dispatcher.py +0 -0
  32. {scurrypy-0.5/discord → scurrypy-0.5.2/scurrypy}/error.py +0 -0
  33. {scurrypy-0.5/discord → scurrypy-0.5.2/scurrypy}/events/__init__.py +0 -0
  34. {scurrypy-0.5/discord → scurrypy-0.5.2/scurrypy}/events/channel_events.py +0 -0
  35. {scurrypy-0.5/discord → scurrypy-0.5.2/scurrypy}/events/guild_events.py +0 -0
  36. {scurrypy-0.5/discord → scurrypy-0.5.2/scurrypy}/events/hello_event.py +0 -0
  37. {scurrypy-0.5/discord → scurrypy-0.5.2/scurrypy}/events/reaction_events.py +0 -0
  38. {scurrypy-0.5/discord → scurrypy-0.5.2/scurrypy}/events/ready_event.py +0 -0
  39. {scurrypy-0.5/discord → scurrypy-0.5.2/scurrypy}/gateway.py +0 -0
  40. {scurrypy-0.5/discord → scurrypy-0.5.2/scurrypy}/http.py +0 -0
  41. {scurrypy-0.5/discord → scurrypy-0.5.2/scurrypy}/logger.py +0 -0
  42. {scurrypy-0.5/discord → scurrypy-0.5.2/scurrypy}/models/__init__.py +0 -0
  43. {scurrypy-0.5/discord → scurrypy-0.5.2/scurrypy}/models/application.py +0 -0
  44. {scurrypy-0.5/discord → scurrypy-0.5.2/scurrypy}/models/emoji.py +0 -0
  45. {scurrypy-0.5/discord → scurrypy-0.5.2/scurrypy}/models/guild.py +0 -0
  46. {scurrypy-0.5/discord → scurrypy-0.5.2/scurrypy}/models/integration.py +0 -0
  47. {scurrypy-0.5/discord → scurrypy-0.5.2/scurrypy}/models/interaction.py +0 -0
  48. {scurrypy-0.5/discord → scurrypy-0.5.2/scurrypy}/models/member.py +0 -0
  49. {scurrypy-0.5/discord → scurrypy-0.5.2/scurrypy}/models/role.py +0 -0
  50. {scurrypy-0.5/discord → scurrypy-0.5.2/scurrypy}/models/user.py +0 -0
  51. {scurrypy-0.5/discord → scurrypy-0.5.2/scurrypy}/parts/__init__.py +0 -0
  52. {scurrypy-0.5/discord → scurrypy-0.5.2/scurrypy}/parts/component_types.py +0 -0
  53. {scurrypy-0.5/discord → scurrypy-0.5.2/scurrypy}/parts/embed.py +0 -0
  54. {scurrypy-0.5/discord → scurrypy-0.5.2/scurrypy}/parts/modal.py +0 -0
  55. {scurrypy-0.5/discord → scurrypy-0.5.2/scurrypy}/parts/role.py +0 -0
  56. {scurrypy-0.5/discord → scurrypy-0.5.2/scurrypy}/resources/__init__.py +0 -0
  57. {scurrypy-0.5/discord → scurrypy-0.5.2/scurrypy}/resources/application.py +0 -0
  58. {scurrypy-0.5/discord → scurrypy-0.5.2/scurrypy}/resources/bot_emojis.py +0 -0
  59. {scurrypy-0.5/discord → scurrypy-0.5.2/scurrypy}/resources/user.py +0 -0
  60. {scurrypy-0.5 → scurrypy-0.5.2}/scurrypy.egg-info/dependency_links.txt +0 -0
  61. {scurrypy-0.5 → scurrypy-0.5.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scurrypy
3
- Version: 0.5
3
+ Version: 0.5.2
4
4
  Summary: Dataclass-driven Discord API Wrapper in Python
5
5
  Author: Furmissile
6
6
  Requires-Python: >=3.10
@@ -21,10 +21,11 @@ A dataclass-driven Discord API wrapper in Python!
21
21
  While this wrapper is mainly used for various squirrel-related shenanigans, it can also be used for more generic bot purposes.
22
22
 
23
23
  ## Features
24
- * Command and event handling
25
- * Declarative style using decorators
26
- * Supports both legacy and new features
27
- * Respects Discord’s rate limits
24
+ * Command, and event handling
25
+ * Unix shell-style wildcards for component routing
26
+ * Declarative style using decorators
27
+ * Supports both legacy and new features
28
+ * Respects Discord’s rate limits
28
29
 
29
30
  ## Notes & Early Status
30
31
  * This is an early version — feedback, ideas, and contributions are very welcome! That said, there may be bumps along the way, so expect occasional bugs and quirks.
@@ -11,10 +11,11 @@ A dataclass-driven Discord API wrapper in Python!
11
11
  While this wrapper is mainly used for various squirrel-related shenanigans, it can also be used for more generic bot purposes.
12
12
 
13
13
  ## Features
14
- * Command and event handling
15
- * Declarative style using decorators
16
- * Supports both legacy and new features
17
- * Respects Discord’s rate limits
14
+ * Command, and event handling
15
+ * Unix shell-style wildcards for component routing
16
+ * Declarative style using decorators
17
+ * Supports both legacy and new features
18
+ * Respects Discord’s rate limits
18
19
 
19
20
  ## Notes & Early Status
20
21
  * This is an early version — feedback, ideas, and contributions are very welcome! That said, there may be bumps along the way, so expect occasional bugs and quirks.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "scurrypy"
7
- version = "0.5"
7
+ version = "0.5.2"
8
8
 
9
9
  description = "Dataclass-driven Discord API Wrapper in Python"
10
10
  readme = "README.md"
@@ -14,10 +14,10 @@ dependencies = []
14
14
 
15
15
  [tool.setuptools]
16
16
  packages = [
17
- "discord",
18
- "discord.dispatch",
19
- "discord.events",
20
- "discord.models",
21
- "discord.resources",
22
- "discord.parts"
17
+ "scurrypy",
18
+ "scurrypy.dispatch",
19
+ "scurrypy.events",
20
+ "scurrypy.models",
21
+ "scurrypy.resources",
22
+ "scurrypy.parts"
23
23
  ]
@@ -1,4 +1,4 @@
1
- # discord
1
+ # scurrypy
2
2
 
3
3
  import importlib
4
4
  from typing import TYPE_CHECKING
@@ -261,110 +261,110 @@ def __getattr__(name: str):
261
261
 
262
262
  mapping = {
263
263
  # top-level
264
- "Logger": "discord.logger",
265
- "Client": "discord.client",
266
- "Intents": "discord.intents",
267
- "set_intents": "discord.intents",
268
- "BaseConfig": "discord.config",
264
+ "Logger": "scurrypy.logger",
265
+ "Client": "scurrypy.client",
266
+ "Intents": "scurrypy.intents",
267
+ "set_intents": "scurrypy.intents",
268
+ "BaseConfig": "scurrypy.config",
269
269
 
270
- 'InteractionTypes': "discord.dispatch.command_dispatcher",
270
+ 'InteractionTypes': "scurrypy.dispatch.command_dispatcher",
271
271
 
272
- "ReadyEvent": "discord.events.ready_event",
272
+ "ReadyEvent": "scurrypy.events.ready_event",
273
273
 
274
- "ReactionAddEvent": "discord.events.reaction_events",
275
- "ReactionRemoveEvent": "discord.events.reaction_events",
276
- "ReactionRemoveEmojiEvent": "discord.events.reaction_events",
277
- "ReactionRemoveAllEvent": "discord.events.reaction_events",
278
-
279
- "GuildCreateEvent": "discord.events.guild_events",
280
- "GuildUpdateEvent": "discord.events.guild_events",
281
- "GuildDeleteEvent": "discord.events.guild_events",
282
-
283
- "MessageCreateEvent": "discord.events.message_events",
284
- "MessageUpdateEvent": "discord.events.message_events",
285
- "MessageDeleteEvent": "discord.events.message_events",
286
-
287
- "GuildChannelCreateEvent": "discord.events.channel_events",
288
- "GuildChannelUpdateEvent": "discord.events.channel_events",
289
- "GuildChannelDeleteEvent": "discord.events.channel_events",
290
- "ChannelPinsUpdateEvent": "discord.events.channel_events",
291
-
292
- "InteractionEvent": "discord.events.interaction_events",
293
-
294
- 'ApplicationModel': "discord.models.application",
295
- 'EmojiModel': "discord.models.emoji",
296
- 'GuildModel': "discord.models.guild",
297
- 'MemberModel': "discord.models.member",
298
- 'UserModel': "discord.models.user",
299
- 'RoleModel': "discord.models.role",
300
-
301
- 'ChannelTypes': "discord.parts.channel",
302
- 'GuildChannel': "discord.parts.channel",
303
-
304
- 'CommandTypes': "discord.parts.command",
305
- 'CommandOptionTypes': "discord.parts.command",
306
- 'SlashCommand': "discord.parts.command",
307
- 'UserCommand': "discord.parts.command",
308
- 'MessageCommand': "discord.parts.command",
309
-
310
- 'ComponentV2Types': "discord.parts.components_v2",
311
- 'SectionPart': "discord.parts.components_v2",
312
- 'TextDisplay': "discord.parts.components_v2",
313
- 'Thumbnail': "discord.parts.components_v2",
314
- 'MediaGalleryItem': "discord.parts.components_v2",
315
- 'MediaGallery': "discord.parts.components_v2",
316
- 'File': "discord.parts.components_v2",
317
- 'SeparatorTypes': "discord.parts.components_v2",
318
- 'Separator': "discord.parts.components_v2",
319
- 'ContainerPart': "discord.parts.components_v2",
320
- 'Label': "discord.parts.components_v2",
321
-
322
- 'ComponentTypes': "discord.parts.components",
323
- 'ActionRowPart': "discord.parts.components",
324
- 'ButtonStyles': "discord.parts.components",
325
- 'Button': "discord.parts.components",
326
- 'SelectOption': "discord.parts.components",
327
- 'StringSelect': "discord.parts.components",
328
- 'TextInputStyles': 'discord.parts.components',
329
- 'TextInput': "discord.parts.components",
330
- 'DefaultValue': "discord.parts.components",
331
- 'UserSelect': "discord.parts.components",
332
- 'RoleSelect': "discord.parts.components",
333
- 'MentionableSelect': "discord.parts.components",
334
- 'ChannelSelect': "discord.parts.components",
274
+ "ReactionAddEvent": "scurrypy.events.reaction_events",
275
+ "ReactionRemoveEvent": "scurrypy.events.reaction_events",
276
+ "ReactionRemoveEmojiEvent": "scurrypy.events.reaction_events",
277
+ "ReactionRemoveAllEvent": "scurrypy.events.reaction_events",
278
+
279
+ "GuildCreateEvent": "scurrypy.events.guild_events",
280
+ "GuildUpdateEvent": "scurrypy.events.guild_events",
281
+ "GuildDeleteEvent": "scurrypy.events.guild_events",
282
+
283
+ "MessageCreateEvent": "scurrypy.events.message_events",
284
+ "MessageUpdateEvent": "scurrypy.events.message_events",
285
+ "MessageDeleteEvent": "scurrypy.events.message_events",
286
+
287
+ "GuildChannelCreateEvent": "scurrypy.events.channel_events",
288
+ "GuildChannelUpdateEvent": "scurrypy.events.channel_events",
289
+ "GuildChannelDeleteEvent": "scurrypy.events.channel_events",
290
+ "ChannelPinsUpdateEvent": "scurrypy.events.channel_events",
291
+
292
+ "InteractionEvent": "scurrypy.events.interaction_events",
293
+
294
+ 'ApplicationModel': "scurrypy.models.application",
295
+ 'EmojiModel': "scurrypy.models.emoji",
296
+ 'GuildModel': "scurrypy.models.guild",
297
+ 'MemberModel': "scurrypy.models.member",
298
+ 'UserModel': "scurrypy.models.user",
299
+ 'RoleModel': "scurrypy.models.role",
300
+
301
+ 'ChannelTypes': "scurrypy.parts.channel",
302
+ 'GuildChannel': "scurrypy.parts.channel",
303
+
304
+ 'CommandTypes': "scurrypy.parts.command",
305
+ 'CommandOptionTypes': "scurrypy.parts.command",
306
+ 'SlashCommand': "scurrypy.parts.command",
307
+ 'UserCommand': "scurrypy.parts.command",
308
+ 'MessageCommand': "scurrypy.parts.command",
309
+
310
+ 'ComponentV2Types': "scurrypy.parts.components_v2",
311
+ 'SectionPart': "scurrypy.parts.components_v2",
312
+ 'TextDisplay': "scurrypy.parts.components_v2",
313
+ 'Thumbnail': "scurrypy.parts.components_v2",
314
+ 'MediaGalleryItem': "scurrypy.parts.components_v2",
315
+ 'MediaGallery': "scurrypy.parts.components_v2",
316
+ 'File': "scurrypy.parts.components_v2",
317
+ 'SeparatorTypes': "scurrypy.parts.components_v2",
318
+ 'Separator': "scurrypy.parts.components_v2",
319
+ 'ContainerPart': "scurrypy.parts.components_v2",
320
+ 'Label': "scurrypy.parts.components_v2",
321
+
322
+ 'ComponentTypes': "scurrypy.parts.components",
323
+ 'ActionRowPart': "scurrypy.parts.components",
324
+ 'ButtonStyles': "scurrypy.parts.components",
325
+ 'Button': "scurrypy.parts.components",
326
+ 'SelectOption': "scurrypy.parts.components",
327
+ 'StringSelect': "scurrypy.parts.components",
328
+ 'TextInputStyles': 'scurrypy.parts.components',
329
+ 'TextInput': "scurrypy.parts.components",
330
+ 'DefaultValue': "scurrypy.parts.components",
331
+ 'UserSelect': "scurrypy.parts.components",
332
+ 'RoleSelect': "scurrypy.parts.components",
333
+ 'MentionableSelect': "scurrypy.parts.components",
334
+ 'ChannelSelect': "scurrypy.parts.components",
335
335
 
336
- 'EmbedAuthor': "discord.parts.embed",
337
- 'EmbedThumbnail': "discord.parts.embed",
338
- 'EmbedField': "discord.parts.embed",
339
- 'EmbedImage': "discord.parts.embed",
340
- 'EmbedFooter': "discord.parts.embed",
341
- 'EmbedPart': "discord.parts.embed",
336
+ 'EmbedAuthor': "scurrypy.parts.embed",
337
+ 'EmbedThumbnail': "scurrypy.parts.embed",
338
+ 'EmbedField': "scurrypy.parts.embed",
339
+ 'EmbedImage': "scurrypy.parts.embed",
340
+ 'EmbedFooter': "scurrypy.parts.embed",
341
+ 'EmbedPart': "scurrypy.parts.embed",
342
342
 
343
- 'MessageFlags': "discord.parts.message",
344
- 'MessageReferenceTypes': "discord.parts.message",
345
- 'MessageReference': "discord.parts.message",
346
- 'Attachment': "discord.parts.message",
347
- 'MessagePart': "discord.parts.message",
343
+ 'MessageFlags': "scurrypy.parts.message",
344
+ 'MessageReferenceTypes': "scurrypy.parts.message",
345
+ 'MessageReference': "scurrypy.parts.message",
346
+ 'Attachment': "scurrypy.parts.message",
347
+ 'MessagePart': "scurrypy.parts.message",
348
348
 
349
- 'ModalPart': "discord.parts.modal",
350
- 'Role': "discord.parts.role",
349
+ 'ModalPart': "scurrypy.parts.modal",
350
+ 'Role': "scurrypy.parts.role",
351
351
 
352
- 'ApplicationFlags': "discord.resources.application",
353
- 'Application': "discord.resources.application",
352
+ 'ApplicationFlags': "scurrypy.resources.application",
353
+ 'Application': "scurrypy.resources.application",
354
354
 
355
- 'BotEmojis': "discord.resources.bot_emojis",
355
+ 'BotEmojis': "scurrypy.resources.bot_emojis",
356
356
 
357
- 'PinnedMessage': "discord.resources.channel",
358
- 'Channel': "discord.resources.channel",
357
+ 'PinnedMessage': "scurrypy.resources.channel",
358
+ 'Channel': "scurrypy.resources.channel",
359
359
 
360
- 'Guild': "discord.resources.guild",
360
+ 'Guild': "scurrypy.resources.guild",
361
361
 
362
- 'InteractionCallbackTypes': "discord.resources.interaction",
363
- 'Interaction': "discord.resources.interaction",
362
+ 'InteractionCallbackTypes': "scurrypy.resources.interaction",
363
+ 'Interaction': "scurrypy.resources.interaction",
364
364
 
365
- 'Message': "discord.resources.message",
365
+ 'Message': "scurrypy.resources.message",
366
366
 
367
- 'User': "discord.resources.user"
367
+ 'User': "scurrypy.resources.user"
368
368
  }
369
369
 
370
370
  module = importlib.import_module(mapping[name])
@@ -5,7 +5,7 @@ from .http import HTTPClient
5
5
  from .logger import Logger
6
6
 
7
7
  class ClientLike(Protocol):
8
- """Exposes a common interface for [`Client`][discord.client.Client]."""
8
+ """Exposes a common interface for [`Client`][scurrypy.client.Client]."""
9
9
  application_id: int
10
10
  """Bot's application ID."""
11
11
 
@@ -25,7 +25,7 @@ class CommandDispatcher:
25
25
  InteractionTypes.MESSAGE_COMPONENT: MessageComponentData,
26
26
  InteractionTypes.MODAL_SUBMIT: ModalData
27
27
  }
28
- """Maps [`InteractionTypes`][discord.dispatch.command_dispatcher.InteractionTypes] to their respective dataclass."""
28
+ """Maps [`InteractionTypes`][scurrypy.dispatch.command_dispatcher.InteractionTypes] to their respective dataclass."""
29
29
 
30
30
  def __init__(self, client: ClientLike):
31
31
  self.application_id = client.application_id
@@ -66,7 +66,7 @@ class CommandDispatcher:
66
66
  await self._http.request(
67
67
  'PUT',
68
68
  f"applications/{self.application_id}/guilds/{guild_id}/commands",
69
- data=[command._to_dict() for command in cmds]
69
+ data=[command.to_dict() for command in cmds]
70
70
  )
71
71
 
72
72
  async def _register_global_commands(self, commands: list):
@@ -76,7 +76,7 @@ class CommandDispatcher:
76
76
  commands (list): list of serialized commands
77
77
  """
78
78
 
79
- global_commands = [command._to_dict() for command in commands]
79
+ global_commands = [command.to_dict() for command in commands]
80
80
 
81
81
  await self._http.request('PUT', f"applications/{self.application_id}/commands", data=global_commands)
82
82
 
@@ -7,25 +7,25 @@ class EventDispatcher:
7
7
  """Central hub for handling Discord Gateway events."""
8
8
 
9
9
  RESOURCE_MAP = { # maps discord events to their respective dataclass (lazy loading)
10
- 'READY': ('discord.events.ready_event', 'ReadyEvent'),
10
+ 'READY': ('scurrypy.events.ready_event', 'ReadyEvent'),
11
11
 
12
- 'GUILD_CREATE': ('discord.events.guild_events', 'GuildCreateEvent'),
13
- 'GUILD_UPDATE': ('discord.events.guild_events', 'GuildUpdateEvent'),
14
- 'GUILD_DELETE': ('discord.events.guild_events', 'GuildDeleteEvent'),
12
+ 'GUILD_CREATE': ('scurrypy.events.guild_events', 'GuildCreateEvent'),
13
+ 'GUILD_UPDATE': ('scurrypy.events.guild_events', 'GuildUpdateEvent'),
14
+ 'GUILD_DELETE': ('scurrypy.events.guild_events', 'GuildDeleteEvent'),
15
15
 
16
- 'CHANNEL_CREATE': ('discord.events.channel_events', 'GuildChannelCreateEvent'),
17
- 'CHANNEL_UPDATE': ('discord.events.channel_events', 'GuildChannelUpdateEvent'),
18
- 'CHANNEL_DELETE': ('discord.events.channel_events', 'GuildChannelDeleteEvent'),
19
- 'CHANNEL_PINS_UPDATE': ('discord.events.channel_events', 'ChannelPinsUpdateEvent'),
16
+ 'CHANNEL_CREATE': ('scurrypy.events.channel_events', 'GuildChannelCreateEvent'),
17
+ 'CHANNEL_UPDATE': ('scurrypy.events.channel_events', 'GuildChannelUpdateEvent'),
18
+ 'CHANNEL_DELETE': ('scurrypy.events.channel_events', 'GuildChannelDeleteEvent'),
19
+ 'CHANNEL_PINS_UPDATE': ('scurrypy.events.channel_events', 'ChannelPinsUpdateEvent'),
20
20
 
21
- 'MESSAGE_CREATE': ('discord.events.message_events', 'MessageCreateEvent'),
22
- 'MESSAGE_UPDATE': ('discord.events.message_events', 'MessageUpdateEvent'),
23
- 'MESSAGE_DELETE': ('discord.events.message_events', 'MessageDeleteEvent'),
21
+ 'MESSAGE_CREATE': ('scurrypy.events.message_events', 'MessageCreateEvent'),
22
+ 'MESSAGE_UPDATE': ('scurrypy.events.message_events', 'MessageUpdateEvent'),
23
+ 'MESSAGE_DELETE': ('scurrypy.events.message_events', 'MessageDeleteEvent'),
24
24
 
25
- 'MESSAGE_REACTION_ADD': ('discord.events.reaction_events', 'ReactionAddEvent'),
26
- 'MESSAGE_REACTION_REMOVE': ('discord.events.reaction_events', 'ReactionRemoveEvent'),
27
- 'MESSAGE_REACTION_REMOVE_ALL': ('discord.events.reaction_events', 'ReactionRemoveAllEvent'),
28
- 'MESSAGE_REACTION_REMOVE_EMOJI': ('discord.events.reaction_events', 'ReactionRemoveEmojiEvent'),
25
+ 'MESSAGE_REACTION_ADD': ('scurrypy.events.reaction_events', 'ReactionAddEvent'),
26
+ 'MESSAGE_REACTION_REMOVE': ('scurrypy.events.reaction_events', 'ReactionRemoveEvent'),
27
+ 'MESSAGE_REACTION_REMOVE_ALL': ('scurrypy.events.reaction_events', 'ReactionRemoveAllEvent'),
28
+ 'MESSAGE_REACTION_REMOVE_EMOJI': ('scurrypy.events.reaction_events', 'ReactionRemoveEmojiEvent'),
29
29
 
30
30
  # and other events...
31
31
  }
@@ -15,7 +15,7 @@ class ApplicationCommandOptionData(DataModel):
15
15
  """Name of the command option."""
16
16
 
17
17
  type: int
18
- """Type of command option. See [`CommandOptionTypes`][discord.parts.command.CommandOptionTypes]."""
18
+ """Type of command option. See [`CommandOptionTypes`][scurrypy.parts.command.CommandOptionTypes]."""
19
19
 
20
20
  value: str | int | float | bool
21
21
  """Input value for option."""
@@ -153,7 +153,7 @@ class InteractionEvent(DataModel):
153
153
  """Represents the interaction response."""
154
154
 
155
155
  interaction: Interaction
156
- """Interaction resource object. See [`Interaction`][discord.resources.interaction.Interaction]."""
156
+ """Interaction resource object. See [`Interaction`][scurrypy.resources.interaction.Interaction]."""
157
157
 
158
158
  data: Optional[ApplicationCommandData | MessageComponentData | ModalData] = None
159
159
  """Interaction response data."""
@@ -9,25 +9,25 @@ from ..models.member import MemberModel
9
9
  class MessageCreateEvent(DataModel):
10
10
  """Received when a message is created."""
11
11
  message: Message
12
- """Message resource object. See [`Resource.Message`][discord.resources.message.Message]."""
12
+ """Message resource object. See [`Resource.Message`][scurrypy.resources.message.Message]."""
13
13
 
14
14
  guild_id: Optional[int]
15
15
  """Guild ID of the updated message (if in a guild channel)."""
16
16
 
17
17
  member: Optional[MemberModel] # guild-only author info
18
- """Partial Member object of the author of the message. See [`MemberModel`][discord.models.member.MemberModel]."""
18
+ """Partial Member object of the author of the message. See [`MemberModel`][scurrypy.models.member.MemberModel]."""
19
19
 
20
20
  @dataclass
21
21
  class MessageUpdateEvent(DataModel):
22
22
  """Received when a message is updated."""
23
23
  message: Message
24
- """Message resource object. See [`Resource.Message`][discord.resources.message.Message]."""
24
+ """Message resource object. See [`Resource.Message`][scurrypy.resources.message.Message]."""
25
25
 
26
26
  guild_id: Optional[int]
27
27
  """Guild ID of the updated message (if in a guild channel)."""
28
28
 
29
29
  member: Optional[MemberModel]
30
- """Partial Member object of the author of the message. See [`MemberModel`][discord.models.member.MemberModel]."""
30
+ """Partial Member object of the author of the message. See [`MemberModel`][scurrypy.models.member.MemberModel]."""
31
31
 
32
32
  @dataclass
33
33
  class MessageDeleteEvent(DataModel):
@@ -54,7 +54,7 @@ class IntentFlagParams(TypedDict, total=False):
54
54
  message_content: bool
55
55
 
56
56
  def set_intents(**flags: Unpack[IntentFlagParams]):
57
- """Set bot intents using [`IntentFlagParams`][discord.intents.IntentFlagParams].
57
+ """Set bot intents using [`IntentFlagParams`][scurrypy.intents.IntentFlagParams].
58
58
  `Intents.DEFAULT` = (GUILDS | GUILD_MESSAGES) will also be set.
59
59
 
60
60
  Args:
@@ -0,0 +1,71 @@
1
+ from dataclasses import dataclass, fields, is_dataclass
2
+ from typing import get_args, get_origin, Union
3
+
4
+ from .http import HTTPClient
5
+
6
+ @dataclass
7
+ class DataModel:
8
+ """DataModel is a base class for Discord JSONs that provides hydration from raw dicts,
9
+ optional field defaults, and access to HTTP-bound methods.
10
+ """
11
+
12
+ @classmethod
13
+ def from_dict(cls, data: dict, http: 'HTTPClient' = None):
14
+ """Hydrates the given data into the dataclass child.
15
+
16
+ Args:
17
+ data (dict): JSON data
18
+ http (HTTPClient, optional): HTTP session for requests
19
+
20
+ Returns:
21
+ (dataclass): hydrated dataclass
22
+ """
23
+ def unwrap_optional(t):
24
+ if get_origin(t) is Union:
25
+ args = tuple(a for a in get_args(t) if a is not type(None))
26
+ return args[0] if len(args) == 1 else Union[args]
27
+ return t
28
+
29
+ kwargs = {}
30
+ for f in fields(cls):
31
+ v = data.get(f.name)
32
+ t = unwrap_optional(f.type)
33
+
34
+ if v is None:
35
+ kwargs[f.name] = None
36
+ elif is_dataclass(t):
37
+ kwargs[f.name] = t.from_dict(v, http)
38
+ elif get_origin(t) is list:
39
+ lt = get_args(t)[0]
40
+ kwargs[f.name] = [lt.from_dict(x, http) if is_dataclass(lt) else x for x in v]
41
+ else:
42
+ try:
43
+ kwargs[f.name] = t(v)
44
+ except Exception:
45
+ kwargs[f.name] = v # fallback to raw
46
+
47
+ inst = cls(**kwargs)
48
+ if http: inst._http = http
49
+ return inst
50
+
51
+ def to_dict(self):
52
+ """Recursively turns the dataclass into a dictionary and drops empty fields.
53
+
54
+ Returns:
55
+ (dict): serialized dataclasss
56
+ """
57
+ def serialize(val):
58
+ if isinstance(val, list):
59
+ return [serialize(v) for v in val if v is not None]
60
+ if isinstance(val, DataModel):
61
+ return val.to_dict()
62
+ return val
63
+
64
+ result = {}
65
+ for f in fields(self):
66
+ if f.name.startswith('_'):
67
+ continue
68
+ val = getattr(self, f.name)
69
+ # if val not in (None, [], {}, "", 0):
70
+ result[f.name] = serialize(val)
71
+ return result
@@ -27,7 +27,7 @@ class GuildChannel(DataModel):
27
27
  """Name of the channel."""
28
28
 
29
29
  type: Optional[int] = None
30
- """Type of channel. See [`ChannelTypes`][discord.parts.channel.ChannelTypes]."""
30
+ """Type of channel. See [`ChannelTypes`][scurrypy.parts.channel.ChannelTypes]."""
31
31
 
32
32
  topic: Optional[str] = None
33
33
  """Topic of channel."""
@@ -42,7 +42,7 @@ class CommandOption(DataModel):
42
42
  """Option for a slash command."""
43
43
 
44
44
  type: int
45
- """Type of option. See [`CommandOptionTypes`][discord.parts.command.CommandOptionTypes]."""
45
+ """Type of option. See [`CommandOptionTypes`][scurrypy.parts.command.CommandOptionTypes]."""
46
46
 
47
47
  name: str
48
48
  """Name of option."""
@@ -67,7 +67,7 @@ class SlashCommand(DataModel):
67
67
  """Parameters or options for the command."""
68
68
 
69
69
  type: int = field(init=False, default=CommandTypes.CHAT_INPUT)
70
- """Command type. Always `CommandTypes.CHAT_INPUT` for this class. See [`CommandTypes`][discord.parts.command.CommandTypes]."""
70
+ """Command type. Always `CommandTypes.CHAT_INPUT` for this class. See [`CommandTypes`][scurrypy.parts.command.CommandTypes]."""
71
71
 
72
72
  @dataclass
73
73
  class UserCommand(DataModel):
@@ -77,7 +77,7 @@ class UserCommand(DataModel):
77
77
  """Name of the command."""
78
78
 
79
79
  type: int = field(init=False, default=CommandTypes.USER_COMMAND)
80
- """Command type. Always `CommandTypes.USER_COMMAND` for this class. See [`CommandTypes`][discord.parts.command.CommandTypes]."""
80
+ """Command type. Always `CommandTypes.USER_COMMAND` for this class. See [`CommandTypes`][scurrypy.parts.command.CommandTypes]."""
81
81
 
82
82
  @dataclass
83
83
  class MessageCommand(DataModel):
@@ -87,4 +87,4 @@ class MessageCommand(DataModel):
87
87
  """Name of the command."""
88
88
 
89
89
  type: int = field(init=False, default=CommandTypes.MESSAGE_COMMAND)
90
- """Command type. Always `CommandTypes.MESSAGE_COMMAND` for this class. See [`CommandTypes`][discord.parts.command.CommandTypes]."""
90
+ """Command type. Always `CommandTypes.MESSAGE_COMMAND` for this class. See [`CommandTypes`][scurrypy.parts.command.CommandTypes]."""
@@ -24,7 +24,7 @@ class ActionRowPart(DataModel, ContainerChild):
24
24
  """Up to 5 interactive button components or a single select component."""
25
25
 
26
26
  type: int = field(init=False, default=ComponentTypes.ACTION_ROW)
27
- """Component type. Always `ComponentTypes.ACTION_ROW` for this class. See [`ComponentTypes`][discord.parts.components.ComponentTypes]."""
27
+ """Component type. Always `ComponentTypes.ACTION_ROW` for this class. See [`ComponentTypes`][scurrypy.parts.components.ComponentTypes]."""
28
28
 
29
29
  class ButtonStyles:
30
30
  """Represents button styles for a Button component."""
@@ -49,7 +49,7 @@ class Button(DataModel, ActionRowChild, SectionAccessory):
49
49
  """Represents the Button component."""
50
50
 
51
51
  style: int
52
- """A button style. See [`ButtonStyles`][discord.parts.components.ButtonStyles]."""
52
+ """A button style. See [`ButtonStyles`][scurrypy.parts.components.ButtonStyles]."""
53
53
 
54
54
  custom_id: str
55
55
  """ID for the button."""
@@ -67,7 +67,7 @@ class Button(DataModel, ActionRowChild, SectionAccessory):
67
67
  """Whether the button is disabled. Defaults to False."""
68
68
 
69
69
  type: int = field(init=False, default=ComponentTypes.BUTTON)
70
- """Component type. Always `ComponentTypes.BUTTON` for this class. See [`ComponentTypes`][discord.parts.components.ComponentTypes]."""
70
+ """Component type. Always `ComponentTypes.BUTTON` for this class. See [`ComponentTypes`][scurrypy.parts.components.ComponentTypes]."""
71
71
 
72
72
  @dataclass
73
73
  class SelectOption(DataModel):
@@ -96,7 +96,7 @@ class StringSelect(DataModel, ActionRowChild, LabelChild):
96
96
  """ID for the select menu."""
97
97
 
98
98
  options: list[SelectOption] = field(default_factory=list)
99
- """Specified choices in a select menu. See [`SelectOption`][discord.parts.components.SelectOption]."""
99
+ """Specified choices in a select menu. See [`SelectOption`][scurrypy.parts.components.SelectOption]."""
100
100
 
101
101
  placeholder: Optional[str] = None
102
102
  """Placeholder text if nothing is selected or default."""
@@ -114,7 +114,7 @@ class StringSelect(DataModel, ActionRowChild, LabelChild):
114
114
  """Whether select menu is disabled in a message. Defaults to False."""
115
115
 
116
116
  type: int = field(init=False, default=ComponentTypes.STRING_SELECT)
117
- """Component type. Always `ComponentTypes.STRING_SELECT` for this class. See [`ComponentTypes`][discord.parts.components.ComponentTypes]."""
117
+ """Component type. Always `ComponentTypes.STRING_SELECT` for this class. See [`ComponentTypes`][scurrypy.parts.components.ComponentTypes]."""
118
118
 
119
119
  class TextInputStyles:
120
120
  """Represents the types of Text Inputs."""
@@ -130,7 +130,7 @@ class TextInput(DataModel, LabelChild):
130
130
  """Represents the Text Input component."""
131
131
 
132
132
  style: TextInputStyles = TextInputStyles.SHORT
133
- """Text input style. See [`TextInputStyles`][discord.parts.components.TextInputStyles]."""
133
+ """Text input style. See [`TextInputStyles`][scurrypy.parts.components.TextInputStyles]."""
134
134
 
135
135
  custom_id: str = None
136
136
  """ID for the input."""
@@ -154,7 +154,7 @@ class TextInput(DataModel, LabelChild):
154
154
  """Custom placeholder text if the input is empty."""
155
155
 
156
156
  type: int = field(init=False, default=ComponentTypes.TEXT_INPUT)
157
- """Component type. Always `ComponentTypes.TEXT_INPUT` for this class. See [`ComponentTypes`][discord.parts.components.ComponentTypes]."""
157
+ """Component type. Always `ComponentTypes.TEXT_INPUT` for this class. See [`ComponentTypes`][scurrypy.parts.components.ComponentTypes]."""
158
158
 
159
159
  @dataclass
160
160
  class DefaultValue(DataModel):
@@ -178,7 +178,7 @@ class SelectMenu(DataModel):
178
178
 
179
179
  default_values: list[DefaultValue] = field(default_factory=list)
180
180
  """
181
- List of default values for auto-populated select menu components. See [`DefaultValue`][discord.parts.components.DefaultValue].
181
+ List of default values for auto-populated select menu components. See [`DefaultValue`][scurrypy.parts.components.DefaultValue].
182
182
  Number of default values must be in the range of `min_values` to `max_values`.
183
183
  """
184
184
 
@@ -200,25 +200,25 @@ class UserSelect(SelectMenu, ActionRowChild, LabelChild):
200
200
  """Represents the User Select component."""
201
201
 
202
202
  type: int = field(init=False, default=ComponentTypes.USER_SELECT)
203
- """Component type. Always `ComponentTypes.USER_SELECT` for this class. See [`ComponentTypes`][discord.parts.components.ComponentTypes]."""
203
+ """Component type. Always `ComponentTypes.USER_SELECT` for this class. See [`ComponentTypes`][scurrypy.parts.components.ComponentTypes]."""
204
204
 
205
205
  @dataclass
206
206
  class RoleSelect(SelectMenu, ActionRowChild, LabelChild):
207
207
  """Represents the Role Select component."""
208
208
 
209
209
  type: int = field(init=False, default=ComponentTypes.ROLE_SELECT)
210
- """Component type. Always `ComponentTypes.ROLE_SELECT` for this class. See [`ComponentTypes`][discord.parts.components.ComponentTypes]."""
210
+ """Component type. Always `ComponentTypes.ROLE_SELECT` for this class. See [`ComponentTypes`][scurrypy.parts.components.ComponentTypes]."""
211
211
 
212
212
  @dataclass
213
213
  class MentionableSelect(SelectMenu, ActionRowChild, LabelChild):
214
214
  """Represents the Mentionable Select component."""
215
215
 
216
216
  type: int = field(init=False, default=ComponentTypes.MENTIONABLE_SELECT)
217
- """Component type. Always `ComponentTypes.MENTIONABLE_SELECT` for this class. See [`ComponentTypes`][discord.parts.components.ComponentTypes]."""
217
+ """Component type. Always `ComponentTypes.MENTIONABLE_SELECT` for this class. See [`ComponentTypes`][scurrypy.parts.components.ComponentTypes]."""
218
218
 
219
219
  @dataclass
220
220
  class ChannelSelect(SelectMenu, ActionRowChild, LabelChild):
221
221
  """Represents the Channel Select component."""
222
222
 
223
223
  type: int = field(init=False, default=ComponentTypes.CHANNEL_SELECT)
224
- """Component type. Always `ComponentTypes.CHANNEL_SELECT` for this class. See [`ComponentTypes`][discord.parts.components.ComponentTypes]."""
224
+ """Component type. Always `ComponentTypes.CHANNEL_SELECT` for this class. See [`ComponentTypes`][scurrypy.parts.components.ComponentTypes]."""
@@ -25,7 +25,7 @@ class SectionPart(DataModel, ContainerChild):
25
25
  """Component(s) representing the content of the section that is contextually associated to the accessory."""
26
26
 
27
27
  type: int = field(init=False, default=ComponentV2Types.SECTION)
28
- """Component type. Always `ComponentV2Types.SECTION` for this class. See [`ComponentV2Types`][discord.parts.components_v2.ComponentV2Types]."""
28
+ """Component type. Always `ComponentV2Types.SECTION` for this class. See [`ComponentV2Types`][scurrypy.parts.components_v2.ComponentV2Types]."""
29
29
 
30
30
  @dataclass
31
31
  class TextDisplay(DataModel, ContainerChild, SectionChild):
@@ -35,7 +35,7 @@ class TextDisplay(DataModel, ContainerChild, SectionChild):
35
35
  """Text that will be displayed similar to a message."""
36
36
 
37
37
  type: int = field(init=False, default=ComponentV2Types.TEXT_DISPLAY)
38
- """Component type. Always `ComponentV2Types.TEXT_DISPLAY` for this class. See [`ComponentV2Types`][discord.parts.components_v2.ComponentV2Types]."""
38
+ """Component type. Always `ComponentV2Types.TEXT_DISPLAY` for this class. See [`ComponentV2Types`][scurrypy.parts.components_v2.ComponentV2Types]."""
39
39
 
40
40
  @dataclass
41
41
  class Thumbnail(DataModel, SectionAccessory):
@@ -51,7 +51,7 @@ class Thumbnail(DataModel, SectionAccessory):
51
51
  """Whether the thumbnail should be a spoiler (or blurred out)."""
52
52
 
53
53
  type: int = field(init=False, default=ComponentV2Types.THUMBNAIL)
54
- """Component type. Always `ComponentV2Types.THUMBNAIL` for this class. See [`ComponentV2Types`][discord.parts.components_v2.ComponentV2Types]."""
54
+ """Component type. Always `ComponentV2Types.THUMBNAIL` for this class. See [`ComponentV2Types`][scurrypy.parts.components_v2.ComponentV2Types]."""
55
55
 
56
56
  @dataclass
57
57
  class MediaGalleryItem(DataModel):
@@ -71,10 +71,10 @@ class MediaGallery(DataModel, ContainerChild):
71
71
  """Represents the Media Gallery component."""
72
72
 
73
73
  items: list[MediaGalleryItem] = field(default_factory=list)
74
- """1 to 10 nedia gallery items. See [`MediaGalleryItem`][discord.parts.components_v2.MediaGalleryItem]."""
74
+ """1 to 10 nedia gallery items. See [`MediaGalleryItem`][scurrypy.parts.components_v2.MediaGalleryItem]."""
75
75
 
76
76
  type: int = field(init=False, default=ComponentV2Types.MEDIA_GALLERY)
77
- """Component type. Always `ComponentV2Types.MEDIA_GALLERY` for this class. See [`ComponentV2Types`][discord.parts.components_v2.ComponentV2Types]."""
77
+ """Component type. Always `ComponentV2Types.MEDIA_GALLERY` for this class. See [`ComponentV2Types`][scurrypy.parts.components_v2.ComponentV2Types]."""
78
78
 
79
79
  @dataclass
80
80
  class File(DataModel, ContainerChild):
@@ -87,7 +87,7 @@ class File(DataModel, ContainerChild):
87
87
  """Whether the thumbnail should be a spoiler (or blurred out)."""
88
88
 
89
89
  type: int = field(init=False, default=ComponentV2Types.FILE)
90
- """Component type. Always `ComponentV2Types.File` for this class. See [`ComponentV2Types`][discord.parts.components_v2.ComponentV2Types]."""
90
+ """Component type. Always `ComponentV2Types.File` for this class. See [`ComponentV2Types`][scurrypy.parts.components_v2.ComponentV2Types]."""
91
91
 
92
92
  class SeparatorTypes:
93
93
  """Represents separator types constants."""
@@ -106,10 +106,10 @@ class Separator(DataModel, ContainerChild):
106
106
  """Whether a visual divider should be displayed in the component. Defaults to True."""
107
107
 
108
108
  spacing: Optional[int] = SeparatorTypes.SMALL_PADDING
109
- """Size of separator padding. Defaults to `SMALL_PADDING`. See [`SeparatorTypes`][discord.parts.components_v2.SeparatorTypes]."""
109
+ """Size of separator padding. Defaults to `SMALL_PADDING`. See [`SeparatorTypes`][scurrypy.parts.components_v2.SeparatorTypes]."""
110
110
 
111
111
  type: int = field(init=False, default=ComponentV2Types.SEPARATOR)
112
- """Component type. Always `ComponentV2Types.SEPARATOR` for this class. See [`ComponentV2Types`][discord.parts.components_v2.ComponentV2Types]."""
112
+ """Component type. Always `ComponentV2Types.SEPARATOR` for this class. See [`ComponentV2Types`][scurrypy.parts.components_v2.ComponentV2Types]."""
113
113
 
114
114
  @dataclass
115
115
  class ContainerPart(DataModel):
@@ -125,7 +125,7 @@ class ContainerPart(DataModel):
125
125
  """If the container should be blurred out. Defaults to False."""
126
126
 
127
127
  type: int = field(init=False, default=ComponentV2Types.CONTAINER)
128
- """Component type. Always `ComponentV2Types.CONTAINER` for this class. See [`ComponentV2Types`][discord.parts.components_v2.ComponentV2Types]."""
128
+ """Component type. Always `ComponentV2Types.CONTAINER` for this class. See [`ComponentV2Types`][scurrypy.parts.components_v2.ComponentV2Types]."""
129
129
 
130
130
  @dataclass
131
131
  class Label(DataModel):
@@ -141,4 +141,4 @@ class Label(DataModel):
141
141
  """An optional description text for the label."""
142
142
 
143
143
  type: int = field(init=False, default=ComponentV2Types.LABEL)
144
- """Component type. Always `ComponentV2Types.LABEL` for this class. See [`ComponentV2Types`][discord.parts.components_v2.ComponentV2Types]."""
144
+ """Component type. Always `ComponentV2Types.LABEL` for this class. See [`ComponentV2Types`][scurrypy.parts.components_v2.ComponentV2Types]."""
@@ -27,7 +27,7 @@ class MessageFlags:
27
27
  """This message includes Discord's V2 Components."""
28
28
 
29
29
  class MessageFlagParams(TypedDict, total=False):
30
- """Parameters for setting message flags. See [`MessageFlags`][discord.parts.message.MessageFlags]."""
30
+ """Parameters for setting message flags. See [`MessageFlags`][scurrypy.parts.message.MessageFlags]."""
31
31
  crossposted: bool
32
32
  is_crosspost: bool
33
33
  suppress_embeds: bool
@@ -59,7 +59,7 @@ class MessageReference(DataModel):
59
59
  """
60
60
 
61
61
  type: int = MessageReferenceTypes.DEFAULT
62
- """Type of reference. Defaults to `DEFAULT`. See [`MessageReferenceTypes`][discord.parts.message.MessageReferenceTypes]."""
62
+ """Type of reference. Defaults to `DEFAULT`. See [`MessageReferenceTypes`][scurrypy.parts.message.MessageReferenceTypes]."""
63
63
 
64
64
  @dataclass
65
65
  class Attachment(DataModel):
@@ -77,7 +77,7 @@ class Attachment(DataModel):
77
77
  description: str
78
78
  """Description of the file."""
79
79
 
80
- def _to_dict(self):
80
+ def to_dict(self):
81
81
  return {
82
82
  'id': self.id,
83
83
  'filename': self.filename,
@@ -92,7 +92,7 @@ class MessagePart(DataModel):
92
92
  """Message text content."""
93
93
 
94
94
  flags: Optional[int] = 0
95
- """Message flags. See [`MessageFlags`][discord.parts.message.MessageFlags]."""
95
+ """Message flags. See [`MessageFlags`][scurrypy.parts.message.MessageFlags]."""
96
96
 
97
97
  components: Optional[list[ActionRowPart | ContainerPart]] = field(default_factory=list)
98
98
  """Components to be attached to this message."""
@@ -148,7 +148,7 @@ class Channel(DataModel):
148
148
  if isinstance(message, str):
149
149
  message = MessagePart(content=message)
150
150
 
151
- data = await self._http.request("POST", f"/channels/{self.id}/messages", data=message._to_dict())
151
+ data = await self._http.request("POST", f"/channels/{self.id}/messages", data=message.to_dict())
152
152
 
153
153
  return Message.from_dict(data, self._http)
154
154
 
@@ -164,7 +164,7 @@ class Channel(DataModel):
164
164
  Returns:
165
165
  (Channel): The updated channel object
166
166
  """
167
- data = await self._http.request("PATCH", f"/channels/{self.id}", data=channel._to_dict())
167
+ data = await self._http.request("PATCH", f"/channels/{self.id}", data=channel.to_dict())
168
168
  self._update(data)
169
169
 
170
170
  return self
@@ -136,7 +136,7 @@ class Guild(DataModel):
136
136
  Returns:
137
137
  (Channel): the created channel
138
138
  """
139
- data = await self._http.request('POST', f'/guilds/{self.id}/channels', data=channel._to_dict())
139
+ data = await self._http.request('POST', f'/guilds/{self.id}/channels', data=channel.to_dict())
140
140
 
141
141
  return Channel.from_dict(data, self._http)
142
142
 
@@ -233,7 +233,7 @@ class Guild(DataModel):
233
233
  Returns:
234
234
  (RoleModel): new role data
235
235
  """
236
- data = await self._http.request('POST', f'/guilds/{self.id}/roles', data=role._to_dict())
236
+ data = await self._http.request('POST', f'/guilds/{self.id}/roles', data=role.to_dict())
237
237
 
238
238
  return RoleModel.from_dict(data)
239
239
 
@@ -249,7 +249,7 @@ class Guild(DataModel):
249
249
  Returns:
250
250
  (RoleModel): role with changes
251
251
  """
252
- data = await self._http.request('PATCH', f'/guilds/{self.id}/roles/{role_id}', data=role._to_dict())
252
+ data = await self._http.request('PATCH', f'/guilds/{self.id}/roles/{role_id}', data=role.to_dict())
253
253
 
254
254
  return RoleModel.from_dict(data)
255
255
 
@@ -69,7 +69,7 @@ class Interaction(DataModel):
69
69
  """HTTP session for requests."""
70
70
 
71
71
  type: int
72
- """Type of interaction. See [`InteractionTypes`][discord.dispatch.command_dispatcher.InteractionTypes]."""
72
+ """Type of interaction. See [`InteractionTypes`][scurrypy.dispatch.command_dispatcher.InteractionTypes]."""
73
73
 
74
74
  channel_id: int
75
75
  """ID of the channel where the interaction was sent."""
@@ -115,7 +115,7 @@ class Interaction(DataModel):
115
115
 
116
116
  content = {
117
117
  'type': InteractionCallbackTypes.CHANNEL_MESSAGE_WITH_SOURCE,
118
- 'data': message._to_dict()
118
+ 'data': message.to_dict()
119
119
  }
120
120
 
121
121
  params = {'with_response': with_response}
@@ -146,7 +146,7 @@ class Interaction(DataModel):
146
146
 
147
147
  content = {
148
148
  'type': InteractionCallbackTypes.UPDATE_MESSAGE,
149
- 'data': message._to_dict()
149
+ 'data': message.to_dict()
150
150
  }
151
151
 
152
152
  await self._http.request(
@@ -169,7 +169,7 @@ class Interaction(DataModel):
169
169
 
170
170
  content = {
171
171
  'type': InteractionCallbackTypes.MODAL,
172
- 'data': modal._to_dict()
172
+ 'data': modal.to_dict()
173
173
  }
174
174
 
175
175
  await self._http.request(
@@ -68,7 +68,7 @@ class Message(DataModel):
68
68
  data = await self._http.request(
69
69
  "POST",
70
70
  f"/channels/{self.channel_id}/messages",
71
- data=message._to_dict(),
71
+ data=message.to_dict(),
72
72
  files=[fp.path for fp in message.attachments] if message.attachments else None
73
73
  )
74
74
  return Message.from_dict(data, self._http)
@@ -88,7 +88,7 @@ class Message(DataModel):
88
88
  data = await self._http.request(
89
89
  "PATCH",
90
90
  f"/channels/{self.channel_id}/messages/{self.id}",
91
- data=message._to_dict(),
91
+ data=message.to_dict(),
92
92
  files=[fp.path for fp in message.attachments] if message.attachments else None)
93
93
 
94
94
  self._update(data)
@@ -110,7 +110,7 @@ class Message(DataModel):
110
110
  await self._http.request(
111
111
  'POST',
112
112
  f"/channels/{self.channel_id}/messages",
113
- data=message._to_dict(),
113
+ data=message.to_dict(),
114
114
  files=[fp.path for fp in message.attachments] if message.attachments else None)
115
115
 
116
116
  async def crosspost(self):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scurrypy
3
- Version: 0.5
3
+ Version: 0.5.2
4
4
  Summary: Dataclass-driven Discord API Wrapper in Python
5
5
  Author: Furmissile
6
6
  Requires-Python: >=3.10
@@ -21,10 +21,11 @@ A dataclass-driven Discord API wrapper in Python!
21
21
  While this wrapper is mainly used for various squirrel-related shenanigans, it can also be used for more generic bot purposes.
22
22
 
23
23
  ## Features
24
- * Command and event handling
25
- * Declarative style using decorators
26
- * Supports both legacy and new features
27
- * Respects Discord’s rate limits
24
+ * Command, and event handling
25
+ * Unix shell-style wildcards for component routing
26
+ * Declarative style using decorators
27
+ * Supports both legacy and new features
28
+ * Respects Discord’s rate limits
28
29
 
29
30
  ## Notes & Early Status
30
31
  * This is an early version — feedback, ideas, and contributions are very welcome! That said, there may be bumps along the way, so expect occasional bugs and quirks.
@@ -0,0 +1,56 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ scurrypy/__init__.py
5
+ scurrypy/client.py
6
+ scurrypy/client_like.py
7
+ scurrypy/config.py
8
+ scurrypy/error.py
9
+ scurrypy/gateway.py
10
+ scurrypy/http.py
11
+ scurrypy/intents.py
12
+ scurrypy/logger.py
13
+ scurrypy/model.py
14
+ scurrypy.egg-info/PKG-INFO
15
+ scurrypy.egg-info/SOURCES.txt
16
+ scurrypy.egg-info/dependency_links.txt
17
+ scurrypy.egg-info/top_level.txt
18
+ scurrypy/dispatch/__init__.py
19
+ scurrypy/dispatch/command_dispatcher.py
20
+ scurrypy/dispatch/event_dispatcher.py
21
+ scurrypy/dispatch/prefix_dispatcher.py
22
+ scurrypy/events/__init__.py
23
+ scurrypy/events/channel_events.py
24
+ scurrypy/events/guild_events.py
25
+ scurrypy/events/hello_event.py
26
+ scurrypy/events/interaction_events.py
27
+ scurrypy/events/message_events.py
28
+ scurrypy/events/reaction_events.py
29
+ scurrypy/events/ready_event.py
30
+ scurrypy/models/__init__.py
31
+ scurrypy/models/application.py
32
+ scurrypy/models/emoji.py
33
+ scurrypy/models/guild.py
34
+ scurrypy/models/integration.py
35
+ scurrypy/models/interaction.py
36
+ scurrypy/models/member.py
37
+ scurrypy/models/role.py
38
+ scurrypy/models/user.py
39
+ scurrypy/parts/__init__.py
40
+ scurrypy/parts/channel.py
41
+ scurrypy/parts/command.py
42
+ scurrypy/parts/component_types.py
43
+ scurrypy/parts/components.py
44
+ scurrypy/parts/components_v2.py
45
+ scurrypy/parts/embed.py
46
+ scurrypy/parts/message.py
47
+ scurrypy/parts/modal.py
48
+ scurrypy/parts/role.py
49
+ scurrypy/resources/__init__.py
50
+ scurrypy/resources/application.py
51
+ scurrypy/resources/bot_emojis.py
52
+ scurrypy/resources/channel.py
53
+ scurrypy/resources/guild.py
54
+ scurrypy/resources/interaction.py
55
+ scurrypy/resources/message.py
56
+ scurrypy/resources/user.py
@@ -0,0 +1 @@
1
+ scurrypy
@@ -1,90 +0,0 @@
1
- from dataclasses import dataclass, fields, is_dataclass
2
- from typing import get_args, get_origin, Union
3
-
4
- from .http import HTTPClient
5
-
6
- @dataclass
7
- class DataModel:
8
- """DataModel is a base class for Discord JSONs that provides hydration from raw dicts,
9
- optional field defaults, and access to HTTP-bound methods.
10
- """
11
-
12
- @classmethod
13
- def from_dict(cls, data: dict, http: HTTPClient = None):
14
- """Hydrates the given data into the dataclass child.
15
-
16
- Args:
17
- data (dict): JSON data
18
- http (HTTPClient, optional): HTTP session for requests
19
-
20
- Returns:
21
- (dataclass): hydrated dataclass
22
- """
23
- kwargs = {}
24
-
25
- def unwrap_optional(typ):
26
- """Remove NoneType from Optional or leave Union as-is."""
27
- if get_origin(typ) is Union:
28
- args = tuple(a for a in get_args(typ) if a is not type(None))
29
- if len(args) == 1:
30
- return args[0] # single type left
31
- else:
32
- return Union[args] # multi-type union remains
33
- return typ
34
-
35
- for field in fields(cls):
36
- # property must be in given json!
37
- value = data.get(field.name)
38
-
39
- inner_type = unwrap_optional(field.type)
40
-
41
- # Handle None
42
- if value is None:
43
- kwargs[field.name] = None
44
- # Integers stored as strings
45
- elif isinstance(value, str) and value.isdigit():
46
- kwargs[field.name] = int(value)
47
- # Nested dataclass
48
- elif is_dataclass(inner_type):
49
- kwargs[field.name] = inner_type.from_dict(value, http)
50
- # List type
51
- elif get_origin(inner_type) is list:
52
- list_type = get_args(inner_type)[0]
53
- kwargs[field.name] = [
54
- list_type.from_dict(v, http) if is_dataclass(list_type) else v
55
- for v in value
56
- ]
57
- # Everything else (primitive, Union of primitives)
58
- else:
59
- kwargs[field.name] = value
60
-
61
- instance = cls(**kwargs)
62
-
63
- # attach HTTP if given
64
- if http:
65
- instance._http = http
66
-
67
- return instance
68
-
69
- def _to_dict(self):
70
- """Recursively turns the dataclass into a dictionary and drops empty fields.
71
-
72
- Returns:
73
- (dict): serialized dataclasss
74
- """
75
- def serialize(value):
76
- if isinstance(value, list):
77
- return [serialize(v) for v in value]
78
- if isinstance(value, DataModel):
79
- return value._to_dict()
80
- return value
81
-
82
- result = {}
83
- for f in fields(self):
84
- if f.name.startswith('_'): # ignore private fields
85
- continue
86
- value = getattr(self, f.name)
87
- if value not in (None, [], {}, "", 0):
88
- result[f.name] = serialize(value)
89
-
90
- return result
@@ -1,56 +0,0 @@
1
- LICENSE
2
- README.md
3
- pyproject.toml
4
- discord/__init__.py
5
- discord/client.py
6
- discord/client_like.py
7
- discord/config.py
8
- discord/error.py
9
- discord/gateway.py
10
- discord/http.py
11
- discord/intents.py
12
- discord/logger.py
13
- discord/model.py
14
- discord/dispatch/__init__.py
15
- discord/dispatch/command_dispatcher.py
16
- discord/dispatch/event_dispatcher.py
17
- discord/dispatch/prefix_dispatcher.py
18
- discord/events/__init__.py
19
- discord/events/channel_events.py
20
- discord/events/guild_events.py
21
- discord/events/hello_event.py
22
- discord/events/interaction_events.py
23
- discord/events/message_events.py
24
- discord/events/reaction_events.py
25
- discord/events/ready_event.py
26
- discord/models/__init__.py
27
- discord/models/application.py
28
- discord/models/emoji.py
29
- discord/models/guild.py
30
- discord/models/integration.py
31
- discord/models/interaction.py
32
- discord/models/member.py
33
- discord/models/role.py
34
- discord/models/user.py
35
- discord/parts/__init__.py
36
- discord/parts/channel.py
37
- discord/parts/command.py
38
- discord/parts/component_types.py
39
- discord/parts/components.py
40
- discord/parts/components_v2.py
41
- discord/parts/embed.py
42
- discord/parts/message.py
43
- discord/parts/modal.py
44
- discord/parts/role.py
45
- discord/resources/__init__.py
46
- discord/resources/application.py
47
- discord/resources/bot_emojis.py
48
- discord/resources/channel.py
49
- discord/resources/guild.py
50
- discord/resources/interaction.py
51
- discord/resources/message.py
52
- discord/resources/user.py
53
- scurrypy.egg-info/PKG-INFO
54
- scurrypy.egg-info/SOURCES.txt
55
- scurrypy.egg-info/dependency_links.txt
56
- scurrypy.egg-info/top_level.txt
@@ -1 +0,0 @@
1
- discord
File without changes
File without changes