PyBugReporter 1.0.7__py3-none-any.whl
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.
- PyBugReporter/__init__.py +0 -0
- PyBugReporter/_version.py +1 -0
- PyBugReporter/requirements.txt +1 -0
- PyBugReporter/src/BugReporter.py +382 -0
- PyBugReporter/src/__init__.py +0 -0
- pybugreporter-1.0.7.dist-info/LICENSE +21 -0
- pybugreporter-1.0.7.dist-info/METADATA +24 -0
- pybugreporter-1.0.7.dist-info/RECORD +10 -0
- pybugreporter-1.0.7.dist-info/WHEEL +5 -0
- pybugreporter-1.0.7.dist-info/top_level.txt +2 -0
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '1.0.7'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
python-graphql-client~=0.4.3
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import sys
|
|
3
|
+
import traceback
|
|
4
|
+
from functools import wraps
|
|
5
|
+
|
|
6
|
+
from python_graphql_client import GraphqlClient
|
|
7
|
+
|
|
8
|
+
class NotCreatedError(Exception):
|
|
9
|
+
"""Raised when someone tries to report a bug to a repo that has not been set up as a reporting destination through setVars.
|
|
10
|
+
"""
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
class BugHandler:
|
|
14
|
+
"""A class for catching exceptions and automatically creating issues on a GitHub repo.
|
|
15
|
+
|
|
16
|
+
Attributes:
|
|
17
|
+
githubKey (str): the key used to make bug reports to our github
|
|
18
|
+
repoName (str): the name of the repository
|
|
19
|
+
orgName (str): the name of the organization
|
|
20
|
+
test (bool): whether to run in testing mode
|
|
21
|
+
extraInfo (bool): whether to include extra information in the bug report
|
|
22
|
+
kwargs: extra info for the bug report
|
|
23
|
+
"""
|
|
24
|
+
githubKey: str = ''
|
|
25
|
+
repoName: str = ''
|
|
26
|
+
orgName: str = ''
|
|
27
|
+
test: bool = False
|
|
28
|
+
|
|
29
|
+
def __init__(self, githubKey: str, repoName: str, orgName: str, test: bool) -> None:
|
|
30
|
+
"""Saves the given information in the BugHandler object.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
githubKey (str): the key to use to make the issue
|
|
34
|
+
repoName (str): the name of the repo to report to
|
|
35
|
+
orgName (str): the organization of the repo
|
|
36
|
+
test (bool): whether or not bugs in this code should actually be reported
|
|
37
|
+
"""
|
|
38
|
+
self.githubKey = githubKey
|
|
39
|
+
self.repoName = repoName
|
|
40
|
+
self.orgName = orgName
|
|
41
|
+
self.test = test
|
|
42
|
+
|
|
43
|
+
class BugReporter:
|
|
44
|
+
"""Sends errors to their corresponding repos.
|
|
45
|
+
|
|
46
|
+
Attributes:
|
|
47
|
+
handlers (dict): the created BugHandlers to use to send reports
|
|
48
|
+
extraInfo (bool): whether or not extra information is being passed in
|
|
49
|
+
repoName (str): the most recent set up repo to send to
|
|
50
|
+
"""
|
|
51
|
+
handlers: dict = {}
|
|
52
|
+
extraInfo: bool = False
|
|
53
|
+
repoName: str
|
|
54
|
+
|
|
55
|
+
def __init__(self, repoName: str, extraInfo: bool, **kwargs) -> None:
|
|
56
|
+
"""Initializes the BugReporter class as a decorator.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
extraInfo (bool): whether to include extra information in the bug report
|
|
60
|
+
**kwargs: extra info for the bug report
|
|
61
|
+
"""
|
|
62
|
+
self.repoName = repoName
|
|
63
|
+
self.extraInfo = extraInfo
|
|
64
|
+
self.kwargs = kwargs
|
|
65
|
+
|
|
66
|
+
@classmethod
|
|
67
|
+
def setVars(cls, githubKey: str, repoName: str, orgName: str, test: bool) -> None:
|
|
68
|
+
"""Sets the necessary variables to make bug reports.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
githubKey (str): the key used to make bug reports to our github
|
|
72
|
+
repoName (str): the name of the repository
|
|
73
|
+
orgName (str): the name of the organization
|
|
74
|
+
test (bool): whether to run in testing mode
|
|
75
|
+
"""
|
|
76
|
+
cls.handlers[repoName] = BugHandler(githubKey, repoName, orgName, test)
|
|
77
|
+
|
|
78
|
+
def __call__(self, func: callable) -> None:
|
|
79
|
+
"""Decorator that catches exceptions and sends a bug report to the github repository.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
func (callable): the function to be decorated
|
|
83
|
+
"""
|
|
84
|
+
@wraps(func)
|
|
85
|
+
def wrapper(*args, **kwargs) -> None:
|
|
86
|
+
"""Wrapper function that catches exceptions and sends a bug report to the github repository.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
*args: the arguments for the function
|
|
90
|
+
**kwargs: the keyword arguments for the function
|
|
91
|
+
"""
|
|
92
|
+
repoName = self.repoName
|
|
93
|
+
try:
|
|
94
|
+
return func(*args, **kwargs)
|
|
95
|
+
except Exception as e:
|
|
96
|
+
self._handleError(e, repoName, *args, **kwargs)
|
|
97
|
+
return wrapper
|
|
98
|
+
|
|
99
|
+
def _handleError(self, e: Exception, repoName: str, *args, **kwargs) -> None:
|
|
100
|
+
"""Handles error by creating a bug report.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
e (Exception): the exception that was raised
|
|
104
|
+
|
|
105
|
+
Raises:
|
|
106
|
+
e: the exception that was raised
|
|
107
|
+
"""
|
|
108
|
+
excType = type(e).__name__
|
|
109
|
+
tb = traceback.extract_tb(sys.exc_info()[2])
|
|
110
|
+
functionName = tb[-1][2]
|
|
111
|
+
|
|
112
|
+
# title for bug report
|
|
113
|
+
title = f"{repoName} had a {excType} error with the {functionName} function"
|
|
114
|
+
|
|
115
|
+
# description for bug report
|
|
116
|
+
description = f'Type: {excType}\nError text: {e}\nFunction Name: {functionName}\n\n{traceback.format_exc()}'
|
|
117
|
+
description += f"\nArguments: {args}\nKeyword Arguments: {kwargs}"
|
|
118
|
+
if self.extraInfo:
|
|
119
|
+
description += f"\nExtra Info: {self.kwargs}"
|
|
120
|
+
|
|
121
|
+
# Check if we need to send a bug report
|
|
122
|
+
if not self.handlers[repoName].test:
|
|
123
|
+
self._sendBugReport(repoName, title, description)
|
|
124
|
+
|
|
125
|
+
print(title)
|
|
126
|
+
print(description)
|
|
127
|
+
raise e
|
|
128
|
+
|
|
129
|
+
def _sendBugReport(self, repoName: str, errorTitle: str, errorMessage: str) -> None:
|
|
130
|
+
"""Sends a bug report to the Github repository.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
errorTitle (str): the title of the error
|
|
134
|
+
errorMessage (str): the error message
|
|
135
|
+
"""
|
|
136
|
+
client = GraphqlClient(endpoint="https://api.github.com/graphql")
|
|
137
|
+
headers = {"Authorization": f"Bearer {self.handlers[repoName].githubKey}"}
|
|
138
|
+
|
|
139
|
+
# query variables
|
|
140
|
+
repoId = self._getRepoId(self.handlers[repoName])
|
|
141
|
+
bugLabel = "LA_kwDOJ3JPj88AAAABU1q15w"
|
|
142
|
+
autoLabel = "LA_kwDOJ3JPj88AAAABU1q2DA"
|
|
143
|
+
|
|
144
|
+
# Create new issue
|
|
145
|
+
createIssue = """
|
|
146
|
+
mutation createIssue($input: CreateIssueInput!) {
|
|
147
|
+
createIssue(input: $input) {
|
|
148
|
+
issue {
|
|
149
|
+
id
|
|
150
|
+
title
|
|
151
|
+
body
|
|
152
|
+
repository {
|
|
153
|
+
name
|
|
154
|
+
}
|
|
155
|
+
labels(first: 10) {
|
|
156
|
+
nodes {
|
|
157
|
+
name
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
"""
|
|
164
|
+
|
|
165
|
+
variables = {
|
|
166
|
+
"input": {
|
|
167
|
+
"repositoryId": repoId,
|
|
168
|
+
"title": errorTitle,
|
|
169
|
+
"body": errorMessage,
|
|
170
|
+
"labelIds": [bugLabel, autoLabel]
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
issueExists = self._checkIfIssueExists(self.handlers[repoName], errorTitle)
|
|
175
|
+
|
|
176
|
+
if (issueExists == False):
|
|
177
|
+
result = asyncio.run(client.execute_async(query=createIssue, variables=variables, headers=headers))
|
|
178
|
+
print('\nThis error has been reported to the Tree Growth team.\n')
|
|
179
|
+
|
|
180
|
+
issueId = result['data']['createIssue']['issue']['id'] # Extract the issue ID
|
|
181
|
+
|
|
182
|
+
# Mutation to add issue to a project
|
|
183
|
+
addToProject = """
|
|
184
|
+
mutation addToProject($projectId: ID!, $contentId: ID!) {
|
|
185
|
+
addProjectV2ItemById(input: {projectId: $projectId, contentId: $contentId}) {
|
|
186
|
+
item {
|
|
187
|
+
id
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
"""
|
|
192
|
+
|
|
193
|
+
# Replace with your actual project ID
|
|
194
|
+
projectId = self.getProjectId(repoName, "Tree Growth Projects")
|
|
195
|
+
|
|
196
|
+
variables = {
|
|
197
|
+
"projectId": projectId,
|
|
198
|
+
"contentId": issueId
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
# Execute the mutation to add the issue to the project
|
|
202
|
+
asyncio.run(client.execute_async(query=addToProject, variables=variables, headers=headers))
|
|
203
|
+
else:
|
|
204
|
+
print('\nOur team is already aware of this issue.\n')
|
|
205
|
+
|
|
206
|
+
def getProjectId(self, repoName: str, projectName: str) -> str:
|
|
207
|
+
"""Retrieves the GitHub project ID for a specified repository and project name."""
|
|
208
|
+
client = GraphqlClient(endpoint="https://api.github.com/graphql")
|
|
209
|
+
headers = {"Authorization": f"Bearer {self.handlers[repoName].githubKey}"}
|
|
210
|
+
|
|
211
|
+
# Define the GraphQL query to list projects for the repository
|
|
212
|
+
query = """
|
|
213
|
+
query getProjectId($owner: String!, $repo: String!) {
|
|
214
|
+
repository(owner: $owner, name: $repo) {
|
|
215
|
+
projectsV2(first: 10) {
|
|
216
|
+
nodes {
|
|
217
|
+
id
|
|
218
|
+
title
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
"""
|
|
224
|
+
|
|
225
|
+
variables = {
|
|
226
|
+
"owner": self.handlers[repoName].orgName,
|
|
227
|
+
"repo": repoName
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
# Execute the query
|
|
231
|
+
response = asyncio.run(client.execute_async(query=query, variables=variables, headers=headers))
|
|
232
|
+
projects = response["data"]["repository"]["projectsV2"]["nodes"]
|
|
233
|
+
|
|
234
|
+
# Find the project with the matching name and return its ID
|
|
235
|
+
for project in projects:
|
|
236
|
+
if project["title"] == projectName:
|
|
237
|
+
return project["id"]
|
|
238
|
+
|
|
239
|
+
raise ValueError(f"Project '{projectName}' not found in repository '{repoName}'.")
|
|
240
|
+
|
|
241
|
+
def _checkIfIssueExists(self, handler: BugHandler, errorTitle: str) -> bool:
|
|
242
|
+
"""Checks if an issue already exists in the repository.
|
|
243
|
+
|
|
244
|
+
Args:
|
|
245
|
+
handler (BugHandler): the object of reporting details
|
|
246
|
+
errorTitle (str): the title of the error
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
bool: True if the issue exists, False if it does not
|
|
250
|
+
"""
|
|
251
|
+
client = GraphqlClient(endpoint="https://api.github.com/graphql")
|
|
252
|
+
headers = {"Authorization": f"Bearer {handler.githubKey}"}
|
|
253
|
+
|
|
254
|
+
# query variables
|
|
255
|
+
autoLabel = "auto generated"
|
|
256
|
+
|
|
257
|
+
# Query to return all issues with auto gen label
|
|
258
|
+
findIssue = """
|
|
259
|
+
query findIssue ($login: String = "", $name: String = "", $labels: [String!] = "") {
|
|
260
|
+
organization(login: $login) {
|
|
261
|
+
repository(name: $name) {
|
|
262
|
+
issues(labels: $labels, first: 10, states: [OPEN]) {
|
|
263
|
+
nodes {
|
|
264
|
+
title,
|
|
265
|
+
state
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
"""
|
|
272
|
+
|
|
273
|
+
variables = {
|
|
274
|
+
"login": handler.orgName,
|
|
275
|
+
"name": handler.repoName,
|
|
276
|
+
"labels": autoLabel,
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
result = asyncio.run(client.execute_async(query=findIssue, variables=variables, headers=headers))
|
|
280
|
+
nodes = result['data']['organization']['repository']['issues']['nodes']
|
|
281
|
+
|
|
282
|
+
index = 0
|
|
283
|
+
issueExists = False
|
|
284
|
+
|
|
285
|
+
while (len(nodes) > index) :
|
|
286
|
+
title = nodes[index]['title']
|
|
287
|
+
if (errorTitle == title) :
|
|
288
|
+
issueExists = True
|
|
289
|
+
break
|
|
290
|
+
else:
|
|
291
|
+
index += 1
|
|
292
|
+
|
|
293
|
+
return issueExists
|
|
294
|
+
|
|
295
|
+
def _getRepoId(self, handler: BugHandler) -> str:
|
|
296
|
+
"""Gets the repository ID.
|
|
297
|
+
|
|
298
|
+
Args:
|
|
299
|
+
handler (BugHandler): the object of reporting details
|
|
300
|
+
|
|
301
|
+
Returns:
|
|
302
|
+
str: the repository ID
|
|
303
|
+
"""
|
|
304
|
+
client = GraphqlClient(endpoint="https://api.github.com/graphql")
|
|
305
|
+
headers = {"Authorization": f"Bearer {handler.githubKey}"}
|
|
306
|
+
|
|
307
|
+
# query variables
|
|
308
|
+
getID = """
|
|
309
|
+
query getID($owner: String!, $name: String!) {
|
|
310
|
+
repository(owner: $owner, name: $name) {
|
|
311
|
+
id
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
"""
|
|
315
|
+
|
|
316
|
+
variables = {
|
|
317
|
+
"owner": handler.orgName,
|
|
318
|
+
"name": handler.repoName
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
repoID = asyncio.run(client.execute_async(query=getID, variables=variables, headers=headers))
|
|
322
|
+
return repoID['data']['repository']['id']
|
|
323
|
+
|
|
324
|
+
@classmethod
|
|
325
|
+
def manualBugReport(cls, repoName: str, errorTitle: str, errorMessage: str) -> None:
|
|
326
|
+
"""Manually sends a bug report to the Github repository.
|
|
327
|
+
|
|
328
|
+
Args:
|
|
329
|
+
repoName (str): the name of the repo to report to
|
|
330
|
+
errorTitle (str): the title of the error
|
|
331
|
+
errorMessage (str): the error message
|
|
332
|
+
"""
|
|
333
|
+
if repoName not in cls.handlers:
|
|
334
|
+
raise NotCreatedError(f"{repoName} has not been associated with a reporter")
|
|
335
|
+
handler = cls.handlers[repoName]
|
|
336
|
+
if handler.test == True:
|
|
337
|
+
print('This is a test run and no bug report will be sent.')
|
|
338
|
+
return
|
|
339
|
+
client = GraphqlClient(endpoint="https://api.github.com/graphql")
|
|
340
|
+
headers = {"Authorization": f"Bearer {handler.githubKey}"}
|
|
341
|
+
|
|
342
|
+
# query variables
|
|
343
|
+
repoId = cls._getRepoId(cls, handler)
|
|
344
|
+
bugLabel = "LA_kwDOJ3JPj88AAAABU1q15w"
|
|
345
|
+
autoLabel = "LA_kwDOJ3JPj88AAAABU1q2DA"
|
|
346
|
+
|
|
347
|
+
# Create new issue
|
|
348
|
+
createIssue = """
|
|
349
|
+
mutation createIssue($input: CreateIssueInput!) {
|
|
350
|
+
createIssue(input: $input) {
|
|
351
|
+
issue {
|
|
352
|
+
title
|
|
353
|
+
body
|
|
354
|
+
repository {
|
|
355
|
+
name
|
|
356
|
+
}
|
|
357
|
+
labels(first: 10) {
|
|
358
|
+
nodes {
|
|
359
|
+
name
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
"""
|
|
366
|
+
|
|
367
|
+
variables = {
|
|
368
|
+
"input": {
|
|
369
|
+
"repositoryId": repoId,
|
|
370
|
+
"title": errorTitle,
|
|
371
|
+
"body": errorMessage,
|
|
372
|
+
"labelIds": [bugLabel, autoLabel]
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
issueExists = cls._checkIfIssueExists(cls, handler, errorTitle)
|
|
377
|
+
|
|
378
|
+
if (issueExists == False):
|
|
379
|
+
result = asyncio.run(client.execute_async(query=createIssue, variables=variables, headers=headers))
|
|
380
|
+
print('\nThis error has been reported to the Tree Growth team.\n')
|
|
381
|
+
else:
|
|
382
|
+
print('\nOur team is already aware of this issue.\n')
|
|
File without changes
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 byuawsfhtl
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: PyBugReporter
|
|
3
|
+
Version: 1.0.7
|
|
4
|
+
Summary: A python library for catching thrown exceptions and automatically creating issues on a GitHub repo.
|
|
5
|
+
Home-page: https://github.com/byuawsfhtl/PyBugReporter.git
|
|
6
|
+
Author: Record Linking Lab
|
|
7
|
+
Author-email: recordlinkinglab@gmail.com
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/byuawsfhtl/PyBugReporter/issues
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Requires-Dist: python-graphql-client~=0.4.3
|
|
12
|
+
Dynamic: author
|
|
13
|
+
Dynamic: author-email
|
|
14
|
+
Dynamic: description
|
|
15
|
+
Dynamic: description-content-type
|
|
16
|
+
Dynamic: home-page
|
|
17
|
+
Dynamic: project-url
|
|
18
|
+
Dynamic: requires-dist
|
|
19
|
+
Dynamic: summary
|
|
20
|
+
|
|
21
|
+
# PyBugReporter
|
|
22
|
+
A python library for catching thrown exceptions and automatically creating issues on a GitHub repo.
|
|
23
|
+
|
|
24
|
+
Not recommended for public repos, as a malicious user could spam your issues with fake exceptions.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
PyBugReporter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
PyBugReporter/_version.py,sha256=GIukrVnl8Ykjex2rUbxdz2Ov1PRc_MomFmxwSzRJH9w,21
|
|
3
|
+
PyBugReporter/requirements.txt,sha256=EAW4yz3vd10zafBk_nrBB4X2l569expN2zb7Mj6m920,29
|
|
4
|
+
PyBugReporter/src/BugReporter.py,sha256=w904iTS42i5dNCN9iCh9AHHEEpYjjEVFMi9KawwLxVw,13601
|
|
5
|
+
PyBugReporter/src/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
pybugreporter-1.0.7.dist-info/LICENSE,sha256=y1VMyG67xmRdcGWNYFUPkcniixqP7BGqIC5NwYO-Gtk,1067
|
|
7
|
+
pybugreporter-1.0.7.dist-info/METADATA,sha256=F5G0YywjcgYs2M2ecfBcqRtMvdoZOo_CXLjGC8c7g1w,866
|
|
8
|
+
pybugreporter-1.0.7.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
|
|
9
|
+
pybugreporter-1.0.7.dist-info/top_level.txt,sha256=oMev-fwhS97VtD0Y8a-1sl0MfdCLd4cQoySjJPQaIkE,32
|
|
10
|
+
pybugreporter-1.0.7.dist-info/RECORD,,
|