PyBugReporter 1.0.9__tar.gz → 1.0.10__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.
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: PyBugReporter
3
- Version: 1.0.9
3
+ Version: 1.0.10
4
4
  Summary: A python library for catching thrown exceptions and automatically creating issues on a GitHub repo.
5
5
  Home-page: https://github.com/byuawsfhtl/PyBugReporter.git
6
6
  Author: Record Linking Lab
@@ -9,11 +9,13 @@ Project-URL: Bug Tracker, https://github.com/byuawsfhtl/PyBugReporter/issues
9
9
  Description-Content-Type: text/markdown
10
10
  License-File: LICENSE
11
11
  Requires-Dist: python-graphql-client~=0.4.3
12
+ Requires-Dist: discord.py~=2.0.1
12
13
  Dynamic: author
13
14
  Dynamic: author-email
14
15
  Dynamic: description
15
16
  Dynamic: description-content-type
16
17
  Dynamic: home-page
18
+ Dynamic: license-file
17
19
  Dynamic: project-url
18
20
  Dynamic: requires-dist
19
21
  Dynamic: summary
@@ -0,0 +1 @@
1
+ __version__ = '1.0.10'
@@ -1 +1,2 @@
1
1
  python-graphql-client~=0.4.3
2
+ discord.py~=2.0.1
@@ -2,6 +2,7 @@ import asyncio
2
2
  import sys
3
3
  import traceback
4
4
  from functools import wraps
5
+ from PyBugReporter.src.DiscordBot import DiscordBot
5
6
 
6
7
  from python_graphql_client import GraphqlClient
7
8
 
@@ -25,8 +26,11 @@ class BugHandler:
25
26
  repoName: str = ''
26
27
  orgName: str = ''
27
28
  test: bool = False
29
+ useDiscord: bool = False
30
+ botToken: str = ''
31
+ channelId: str | int = ''
28
32
 
29
- def __init__(self, githubKey: str, repoName: str, orgName: str, test: bool) -> None:
33
+ def __init__(self, githubKey: str, repoName: str, orgName: str, test: bool, useDiscord: bool = False, botToken: str = "", channelId: str | int = "") -> None:
30
34
  """Saves the given information in the BugHandler object.
31
35
 
32
36
  Args:
@@ -34,11 +38,19 @@ class BugHandler:
34
38
  repoName (str): the name of the repo to report to
35
39
  orgName (str): the organization of the repo
36
40
  test (bool): whether or not bugs in this code should actually be reported
41
+ useDiscord (bool): whether to send the bug report to Discord
42
+ botToken (str): the token for the Discord bot
43
+ channelId (str | int): the ID of the Discord channel to send messages to
37
44
  """
38
45
  self.githubKey = githubKey
39
46
  self.repoName = repoName
40
47
  self.orgName = orgName
41
48
  self.test = test
49
+ self.useDiscord = useDiscord
50
+
51
+ if useDiscord:
52
+ self.botToken = botToken
53
+ self.channelId = channelId
42
54
 
43
55
  class BugReporter:
44
56
  """Sends errors to their corresponding repos.
@@ -64,7 +76,7 @@ class BugReporter:
64
76
  self.kwargs = kwargs
65
77
 
66
78
  @classmethod
67
- def setVars(cls, githubKey: str, repoName: str, orgName: str, test: bool) -> None:
79
+ def setVars(cls, githubKey: str, repoName: str, orgName: str, test: bool, useDiscord: bool = False, botToken: str = "", channelId: str = "") -> None:
68
80
  """Sets the necessary variables to make bug reports.
69
81
 
70
82
  Args:
@@ -73,7 +85,7 @@ class BugReporter:
73
85
  orgName (str): the name of the organization
74
86
  test (bool): whether to run in testing mode
75
87
  """
76
- cls.handlers[repoName] = BugHandler(githubKey, repoName, orgName, test)
88
+ cls.handlers[repoName] = BugHandler(githubKey, repoName, orgName, test, useDiscord, botToken, channelId)
77
89
 
78
90
  def __call__(self, func: callable) -> None:
79
91
  """Decorator that catches exceptions and sends a bug report to the github repository.
@@ -118,26 +130,51 @@ class BugReporter:
118
130
  if self.extraInfo:
119
131
  description += f"\nExtra Info: {self.kwargs}"
120
132
 
133
+ # shortened description for discord if too long (shortens the error text)
134
+ start = f"# {title}\n\nType: {excType}\nError text: "
135
+ compress = f"{e}\nTraceback: {traceback.format_exc()}"
136
+ end = f"\n\nFunction Name: {functionName}\nArguments: {args}\nKeyword Arguments: {kwargs}"
137
+ if self.extraInfo:
138
+ end += f"\nExtra Info: {self.kwargs}"
139
+
140
+ staticLength = len(start) + len(end)
141
+ if staticLength > 2000:
142
+ shortDescription = f"# {title}\n\n" + description[:2000 - len(f"# {title}\n\n") - 3] + "..."
143
+ else:
144
+ shortDescription = f"{start}{compress[:2000 - staticLength]}{end}"
145
+
146
+ print(f"SHORT DESCRIPTION with length {len(shortDescription)}:\n{shortDescription}")
147
+
148
+
121
149
  # Check if we need to send a bug report
122
150
  if not self.handlers[repoName].test:
123
- self._sendBugReport(repoName, title, description)
151
+ self._sendBugReport(repoName, title, description, shortDescription)
124
152
 
125
153
  print(title)
126
154
  print(description)
127
155
  raise e
128
156
 
129
- def _sendBugReport(self, repoName: str, errorTitle: str, errorMessage: str) -> None:
157
+ def _sendBugReport(self, repoName: str, errorTitle: str, errorMessage: str, shortErrorMessage: str) -> None:
130
158
  """Sends a bug report to the Github repository.
131
159
 
132
160
  Args:
133
161
  errorTitle (str): the title of the error
134
162
  errorMessage (str): the error message
135
- """
163
+ """
164
+ asyncio.run(self._sendBugReport_async(repoName, errorTitle, errorMessage, shortErrorMessage))
165
+
166
+ async def _sendBugReport_async(self, repoName: str, errorTitle: str, errorMessage: str, shortErrorMessage: str) -> None:
167
+ """Sends a bug report to the Github repository asynchronously.
168
+
169
+ Args:
170
+ errorTitle (str): the title of the error
171
+ errorMessage (str): the error message
172
+ """
136
173
  client = GraphqlClient(endpoint="https://api.github.com/graphql")
137
174
  headers = {"Authorization": f"Bearer {self.handlers[repoName].githubKey}"}
138
175
 
139
176
  # query variables
140
- repoId = self._getRepoId(self.handlers[repoName])
177
+ repoId = await self._getRepoId_async(self.handlers[repoName])
141
178
  bugLabel = "LA_kwDOJ3JPj88AAAABU1q15w"
142
179
  autoLabel = "LA_kwDOJ3JPj88AAAABU1q2DA"
143
180
 
@@ -171,10 +208,15 @@ class BugReporter:
171
208
  }
172
209
  }
173
210
 
174
- issueExists = self._checkIfIssueExists(self.handlers[repoName], errorTitle)
211
+ issueExists = await self._checkIfIssueExists_async(self.handlers[repoName], errorTitle)
175
212
 
176
- if (issueExists == False):
177
- result = asyncio.run(client.execute_async(query=createIssue, variables=variables, headers=headers))
213
+ # Send to Discord if applicable
214
+ if self.handlers[repoName].useDiscord:
215
+ discordBot = DiscordBot(self.handlers[repoName].botToken, self.handlers[repoName].channelId)
216
+ await discordBot.send_message(shortErrorMessage, issueExists)
217
+
218
+ if (not issueExists):
219
+ result = await client.execute_async(query=createIssue, variables=variables, headers=headers)
178
220
  print('\nThis error has been reported to the Tree Growth team.\n')
179
221
 
180
222
  issueId = result['data']['createIssue']['issue']['id'] # Extract the issue ID
@@ -191,7 +233,7 @@ class BugReporter:
191
233
  """
192
234
 
193
235
  # Replace with your actual project ID
194
- projectId = self.getProjectId(repoName, "Tree Growth Projects")
236
+ projectId = await self.getProjectId_async(repoName, "Tree Growth Projects")
195
237
 
196
238
  variables = {
197
239
  "projectId": projectId,
@@ -199,12 +241,11 @@ class BugReporter:
199
241
  }
200
242
 
201
243
  # Execute the mutation to add the issue to the project
202
- asyncio.run(client.execute_async(query=addToProject, variables=variables, headers=headers))
244
+ await client.execute_async(query=addToProject, variables=variables, headers=headers)
203
245
  else:
204
246
  print('\nOur team is already aware of this issue.\n')
205
247
 
206
- def getProjectId(self, repoName: str, projectName: str) -> str:
207
- """Retrieves the GitHub project ID for a specified repository and project name."""
248
+ async def getProjectId_async(self, repoName: str, projectName: str) -> str:
208
249
  client = GraphqlClient(endpoint="https://api.github.com/graphql")
209
250
  headers = {"Authorization": f"Bearer {self.handlers[repoName].githubKey}"}
210
251
 
@@ -228,7 +269,7 @@ class BugReporter:
228
269
  }
229
270
 
230
271
  # Execute the query
231
- response = asyncio.run(client.execute_async(query=query, variables=variables, headers=headers))
272
+ response = await client.execute_async(query=query, variables=variables, headers=headers)
232
273
  projects = response["data"]["repository"]["projectsV2"]["nodes"]
233
274
 
234
275
  # Find the project with the matching name and return its ID
@@ -238,7 +279,7 @@ class BugReporter:
238
279
 
239
280
  raise ValueError(f"Project '{projectName}' not found in repository '{repoName}'.")
240
281
 
241
- def _checkIfIssueExists(self, handler: BugHandler, errorTitle: str) -> bool:
282
+ async def _checkIfIssueExists_async(self, handler: BugHandler, errorTitle: str) -> bool:
242
283
  """Checks if an issue already exists in the repository.
243
284
 
244
285
  Args:
@@ -276,7 +317,7 @@ class BugReporter:
276
317
  "labels": autoLabel,
277
318
  }
278
319
 
279
- result = asyncio.run(client.execute_async(query=findIssue, variables=variables, headers=headers))
320
+ result = await client.execute_async(query=findIssue, variables=variables, headers=headers)
280
321
  nodes = result['data']['organization']['repository']['issues']['nodes']
281
322
 
282
323
  index = 0
@@ -292,7 +333,7 @@ class BugReporter:
292
333
 
293
334
  return issueExists
294
335
 
295
- def _getRepoId(self, handler: BugHandler) -> str:
336
+ async def _getRepoId_async(self, handler: BugHandler) -> str:
296
337
  """Gets the repository ID.
297
338
 
298
339
  Args:
@@ -318,7 +359,7 @@ class BugReporter:
318
359
  "name": handler.repoName
319
360
  }
320
361
 
321
- repoID = asyncio.run(client.execute_async(query=getID, variables=variables, headers=headers))
362
+ repoID = await client.execute_async(query=getID, variables=variables, headers=headers)
322
363
  return repoID['data']['repository']['id']
323
364
 
324
365
  @classmethod
@@ -0,0 +1,79 @@
1
+ import asyncio
2
+ import discord
3
+
4
+ HISTORY_LIMIT = 20
5
+ EMOJI = "‼"
6
+
7
+ class DiscordBot(discord.Client):
8
+ """
9
+ A simple Discord bot that forwards the bug reports to a given Discord channel.
10
+
11
+ Attributes:
12
+ token (str): bot token
13
+ channel_id (int): the ID of the channel to send messages to
14
+ _message (str): message to send
15
+ _alreadySent (bool): whether the message has already been sent
16
+ _done_future (asyncio.Future): a future that is set when the bot is done
17
+ """
18
+ def __init__(self, token: str, channelId: str | int) -> None:
19
+ """
20
+ Initializes the Discord bot with the given token and channel ID.
21
+
22
+ Args:
23
+ token (str): bot token
24
+ channel_id (int): the ID of the channel to send messages to
25
+ """
26
+ self.token = token
27
+ self.channelId = int(channelId)
28
+ self._message = None
29
+ self._alreadySent = False
30
+ self._doneFuture = None
31
+
32
+ intents = discord.Intents(emojis = True,
33
+ guild_reactions = True,
34
+ message_content = True,
35
+ guild_messages = True,
36
+ guilds = True)
37
+ super().__init__(intents=intents)
38
+
39
+ async def send_message(self, message, alreadySent = False):
40
+ """
41
+ Sends a message to the specified channel by setting the variables and starting the bot, then turning it off when finished.
42
+
43
+ Args:
44
+ message (str): The message to send.
45
+ alreadySent (bool): Whether the message has already been sent.
46
+ """
47
+ self._message = message
48
+ self._alreadySent = alreadySent
49
+ self._doneFuture = asyncio.get_running_loop().create_future()
50
+ print("Starting bot...")
51
+ # Start the bot as a background task
52
+ asyncio.create_task(self.start(self.token))
53
+ # Wait until the message is sent and the bot is closed
54
+ await self._doneFuture
55
+
56
+ async def on_ready(self):
57
+ """
58
+ Called when the bot is ready. Also sends the message to the specified channel, or reacts if it's been sent.
59
+ """
60
+ try:
61
+ channel = await self.fetch_channel(self.channelId)
62
+ if channel and not self._alreadySent:
63
+ await channel.send(self._message)
64
+ print(f"Sent message to channel {self.channelId}")
65
+ elif channel and self._alreadySent:
66
+ async for message in channel.history(limit=HISTORY_LIMIT):
67
+ if message.content == self._message:
68
+ await message.add_reaction(EMOJI)
69
+ break
70
+ else:
71
+ print(f"Channel with ID {self.channelId} not found.")
72
+ except Exception as e:
73
+ print(f"Error sending message: {e}")
74
+ finally:
75
+ print("Shutting down bot...")
76
+ await self.close()
77
+ # Mark the future as done so send_message can return
78
+ if self._doneFuture and not self._doneFuture.done():
79
+ self._doneFuture.set_result(True)
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: PyBugReporter
3
- Version: 1.0.9
3
+ Version: 1.0.10
4
4
  Summary: A python library for catching thrown exceptions and automatically creating issues on a GitHub repo.
5
5
  Home-page: https://github.com/byuawsfhtl/PyBugReporter.git
6
6
  Author: Record Linking Lab
@@ -9,11 +9,13 @@ Project-URL: Bug Tracker, https://github.com/byuawsfhtl/PyBugReporter/issues
9
9
  Description-Content-Type: text/markdown
10
10
  License-File: LICENSE
11
11
  Requires-Dist: python-graphql-client~=0.4.3
12
+ Requires-Dist: discord.py~=2.0.1
12
13
  Dynamic: author
13
14
  Dynamic: author-email
14
15
  Dynamic: description
15
16
  Dynamic: description-content-type
16
17
  Dynamic: home-page
18
+ Dynamic: license-file
17
19
  Dynamic: project-url
18
20
  Dynamic: requires-dist
19
21
  Dynamic: summary
@@ -10,4 +10,5 @@ PyBugReporter.egg-info/dependency_links.txt
10
10
  PyBugReporter.egg-info/requires.txt
11
11
  PyBugReporter.egg-info/top_level.txt
12
12
  PyBugReporter/src/BugReporter.py
13
+ PyBugReporter/src/DiscordBot.py
13
14
  PyBugReporter/src/__init__.py
@@ -0,0 +1,2 @@
1
+ python-graphql-client~=0.4.3
2
+ discord.py~=2.0.1
@@ -1 +0,0 @@
1
- __version__ = '1.0.9'
@@ -1 +0,0 @@
1
- python-graphql-client~=0.4.3
File without changes
File without changes
File without changes
File without changes