ghnova 0.3.0__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.
Files changed (60) hide show
  1. ghnova/__init__.py +8 -0
  2. ghnova/__main__.py +8 -0
  3. ghnova/cli/__init__.py +1 -0
  4. ghnova/cli/config/__init__.py +1 -0
  5. ghnova/cli/config/add.py +48 -0
  6. ghnova/cli/config/delete.py +50 -0
  7. ghnova/cli/config/list.py +40 -0
  8. ghnova/cli/config/main.py +27 -0
  9. ghnova/cli/config/update.py +59 -0
  10. ghnova/cli/issue/__init__.py +7 -0
  11. ghnova/cli/issue/create.py +155 -0
  12. ghnova/cli/issue/get.py +119 -0
  13. ghnova/cli/issue/list.py +267 -0
  14. ghnova/cli/issue/lock.py +110 -0
  15. ghnova/cli/issue/main.py +31 -0
  16. ghnova/cli/issue/unlock.py +101 -0
  17. ghnova/cli/issue/update.py +164 -0
  18. ghnova/cli/main.py +117 -0
  19. ghnova/cli/repository/__init__.py +1 -0
  20. ghnova/cli/repository/list.py +201 -0
  21. ghnova/cli/repository/main.py +21 -0
  22. ghnova/cli/user/__init__.py +1 -0
  23. ghnova/cli/user/ctx_info.py +105 -0
  24. ghnova/cli/user/get.py +98 -0
  25. ghnova/cli/user/list.py +78 -0
  26. ghnova/cli/user/main.py +27 -0
  27. ghnova/cli/user/update.py +164 -0
  28. ghnova/cli/utils/__init__.py +7 -0
  29. ghnova/cli/utils/auth.py +67 -0
  30. ghnova/client/__init__.py +8 -0
  31. ghnova/client/async_github.py +121 -0
  32. ghnova/client/base.py +78 -0
  33. ghnova/client/github.py +107 -0
  34. ghnova/config/__init__.py +8 -0
  35. ghnova/config/manager.py +209 -0
  36. ghnova/config/model.py +58 -0
  37. ghnova/issue/__init__.py +8 -0
  38. ghnova/issue/async_issue.py +554 -0
  39. ghnova/issue/base.py +469 -0
  40. ghnova/issue/issue.py +584 -0
  41. ghnova/repository/__init__.py +8 -0
  42. ghnova/repository/async_repository.py +134 -0
  43. ghnova/repository/base.py +124 -0
  44. ghnova/repository/repository.py +134 -0
  45. ghnova/resource/__init__.py +8 -0
  46. ghnova/resource/async_resource.py +88 -0
  47. ghnova/resource/resource.py +88 -0
  48. ghnova/user/__init__.py +8 -0
  49. ghnova/user/async_user.py +285 -0
  50. ghnova/user/base.py +214 -0
  51. ghnova/user/user.py +285 -0
  52. ghnova/utils/__init__.py +16 -0
  53. ghnova/utils/log.py +70 -0
  54. ghnova/utils/response.py +67 -0
  55. ghnova/version.py +11 -0
  56. ghnova-0.3.0.dist-info/METADATA +194 -0
  57. ghnova-0.3.0.dist-info/RECORD +60 -0
  58. ghnova-0.3.0.dist-info/WHEEL +4 -0
  59. ghnova-0.3.0.dist-info/entry_points.txt +2 -0
  60. ghnova-0.3.0.dist-info/licenses/LICENSE +21 -0
ghnova/issue/base.py ADDED
@@ -0,0 +1,469 @@
1
+ """Base class for GitHub Issue resource."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from datetime import datetime
7
+ from typing import Any, Literal
8
+
9
+ logger = logging.getLogger("ghnova")
10
+
11
+
12
+ class BaseIssue:
13
+ """Base class for GitHub Issue resource."""
14
+
15
+ def _list_issues_endpoint(
16
+ self, owner: str | None = None, organization: str | None = None, repository: str | None = None
17
+ ) -> tuple[str, str]:
18
+ """Determine the issues listing endpoint based on owner, organization, and repository.
19
+
20
+ Args:
21
+ owner: The owner of the repository.
22
+ organization: The organization name.
23
+ repository: The repository name.
24
+
25
+ Returns:
26
+ A tuple containing the API endpoint for listing issues and a description of the issue type.
27
+
28
+ """
29
+ if owner is None and organization is None and repository is None:
30
+ return "/issues", "authenticated user issues"
31
+ if owner is None and organization is not None and repository is None:
32
+ return f"/orgs/{organization}/issues", "organization issues"
33
+ if (owner is not None or organization is not None) and repository is not None:
34
+ repo_owner = owner if owner is not None else organization
35
+ return f"/repos/{repo_owner}/{repository}/issues", "repository issues"
36
+ raise ValueError("Invalid combination of owner, organization, and repository parameters.")
37
+
38
+ def _list_issues_helper( # noqa: PLR0912, PLR0913, PLR0915
39
+ self,
40
+ owner: str | None = None,
41
+ organization: str | None = None,
42
+ repository: str | None = None,
43
+ filter_by: Literal["assigned", "created", "mentioned", "subscribed", "all"] | None = None,
44
+ state: Literal["open", "closed", "all"] | None = None,
45
+ labels: list[str] | None = None,
46
+ sort: Literal["created", "updated", "comments"] | None = None,
47
+ direction: Literal["asc", "desc"] | None = None,
48
+ since: datetime | None = None,
49
+ collab: bool | None = None,
50
+ orgs: bool | None = None,
51
+ owned: bool | None = None,
52
+ pulls: bool | None = None,
53
+ issue_type: str | None = None,
54
+ milestone: str | None = None,
55
+ assignee: str | None = None,
56
+ creator: str | None = None,
57
+ mentioned: str | None = None,
58
+ per_page: int = 30,
59
+ page: int = 1,
60
+ **kwargs: Any,
61
+ ) -> tuple[str, dict[str, Any], dict[str, Any]]:
62
+ """List issues with various filtering and sorting options.
63
+
64
+ Supported scenarios:
65
+
66
+ - Authenticated user: Do not provide owner, organization, or repository.
67
+ - Organization issues: Provide organization, but not owner or repository.
68
+ - Repository issues: Provide owner or organization along with repository.
69
+
70
+ Args:
71
+ owner: The owner of the repository.
72
+ organization: The organization name.
73
+ repository: The repository name.
74
+ filter_by: Filter issues by criteria.
75
+ state: The state of the issues to return.
76
+ labels: A list of labels to filter issues by.
77
+ sort: The field to sort issues by.
78
+ direction: The direction of the sort.
79
+ since: Only issues updated at or after this time are returned.
80
+ collab: Include issues from repositories the user collaborates on (for authenticated user issues).
81
+ orgs: Include issues from organizations the user is a member of (for authenticated user issues).
82
+ owned: Include issues from repositories owned by the user (for authenticated user issues).
83
+ pulls: Include pull requests in the issues list (for authenticated user issues).
84
+ issue_type: The type of issues to filter by (for organization issues).
85
+ milestone: Filter issues by milestone (for repository issues).
86
+ assignee: Filter issues by assignee (for repository issues).
87
+ creator: Filter issues by creator (for repository issues).
88
+ mentioned: Filter issues by mentioned user (for repository issues).
89
+ per_page: The number of issues per page.
90
+ page: The page number to retrieve.
91
+ **kwargs: Additional arguments for the request.
92
+
93
+ """
94
+ endpoint, endpoint_type = self._list_issues_endpoint(
95
+ owner=owner, organization=organization, repository=repository
96
+ )
97
+ default_headers = {
98
+ "Accept": "application/vnd.github+json",
99
+ "X-GitHub-Api-Version": "2022-11-28",
100
+ }
101
+ headers = kwargs.get("headers", {})
102
+ headers = {**default_headers, **headers}
103
+ kwargs["headers"] = headers
104
+
105
+ params: dict[str, str | int | bool] = {}
106
+ # Add the common query parameters
107
+ if state is not None:
108
+ params["state"] = state
109
+ if labels is not None:
110
+ params["labels"] = ",".join(labels)
111
+ if sort is not None:
112
+ params["sort"] = sort
113
+ if direction is not None:
114
+ params["direction"] = direction
115
+ if since is not None:
116
+ params["since"] = since.isoformat()
117
+ if per_page is not None:
118
+ params["per_page"] = per_page
119
+ if page is not None:
120
+ params["page"] = page
121
+
122
+ # Add specific query parameters based on issue type
123
+ if endpoint_type == "authenticated user issues":
124
+ if filter_by is not None:
125
+ params["filter"] = filter_by
126
+ if collab is not None:
127
+ params["collab"] = collab
128
+ if orgs is not None:
129
+ params["orgs"] = orgs
130
+ if owned is not None:
131
+ params["owned"] = owned
132
+ if pulls is not None:
133
+ params["pulls"] = pulls
134
+ if issue_type is not None:
135
+ logger.warning("The 'issue_type' parameter is ignored for authenticated user issues.")
136
+ if milestone is not None:
137
+ logger.warning("The 'milestone' parameter is ignored for authenticated user issues.")
138
+ if assignee is not None:
139
+ logger.warning("The 'assignee' parameter is ignored for authenticated user issues.")
140
+ if creator is not None:
141
+ logger.warning("The 'creator' parameter is ignored for authenticated user issues.")
142
+ if mentioned is not None:
143
+ logger.warning("The 'mentioned' parameter is ignored for authenticated user issues.")
144
+ elif endpoint_type == "organization issues":
145
+ if filter_by is not None:
146
+ params["filter"] = filter_by
147
+ if issue_type is not None:
148
+ params["type"] = issue_type
149
+ if collab is not None:
150
+ logger.warning("The 'collab' parameter is ignored for organization issues.")
151
+ if orgs is not None:
152
+ logger.warning("The 'orgs' parameter is ignored for organization issues.")
153
+ if owned is not None:
154
+ logger.warning("The 'owned' parameter is ignored for organization issues.")
155
+ if pulls is not None:
156
+ logger.warning("The 'pulls' parameter is ignored for organization issues.")
157
+ if milestone is not None:
158
+ logger.warning("The 'milestone' parameter is ignored for organization issues.")
159
+ if assignee is not None:
160
+ logger.warning("The 'assignee' parameter is ignored for organization issues.")
161
+ if creator is not None:
162
+ logger.warning("The 'creator' parameter is ignored for organization issues.")
163
+ if mentioned is not None:
164
+ logger.warning("The 'mentioned' parameter is ignored for organization issues.")
165
+ elif endpoint_type == "repository issues":
166
+ if milestone is not None:
167
+ params["milestone"] = milestone
168
+ if assignee is not None:
169
+ params["assignee"] = assignee
170
+ if creator is not None:
171
+ params["creator"] = creator
172
+ if mentioned is not None:
173
+ params["mentioned"] = mentioned
174
+ if filter_by is not None:
175
+ logger.warning("The 'filter_by' parameter is ignored for repository issues.")
176
+ if collab is not None:
177
+ logger.warning("The 'collab' parameter is ignored for repository issues.")
178
+ if orgs is not None:
179
+ logger.warning("The 'orgs' parameter is ignored for repository issues.")
180
+ if owned is not None:
181
+ logger.warning("The 'owned' parameter is ignored for repository issues.")
182
+ if pulls is not None:
183
+ logger.warning("The 'pulls' parameter is ignored for repository issues.")
184
+ if issue_type is not None:
185
+ logger.warning("The 'issue_type' parameter is ignored for repository issues.")
186
+ else:
187
+ raise ValueError(f"Invalid endpoint type determined: {endpoint_type}")
188
+
189
+ return endpoint, params, kwargs
190
+
191
+ def _create_issue_endpoint(self, owner: str, repository: str) -> str:
192
+ """Get the endpoint for creating an issue in a repository.
193
+
194
+ Args:
195
+ owner: The owner of the repository.
196
+ repository: The name of the repository.
197
+
198
+ Returns:
199
+ The API endpoint for creating an issue.
200
+
201
+ """
202
+ return f"/repos/{owner}/{repository}/issues"
203
+
204
+ def _create_issue_helper( # noqa: PLR0913
205
+ self,
206
+ owner: str,
207
+ repository: str,
208
+ title: str,
209
+ body: str | None = None,
210
+ assignee: str | None = None,
211
+ milestone: str | int | None = None,
212
+ labels: list[str] | None = None,
213
+ assignees: list[str] | None = None,
214
+ issue_type: str | None = None,
215
+ **kwargs: Any,
216
+ ) -> tuple[str, dict[str, Any], dict[str, Any]]:
217
+ """Prepare the endpoint and payload for creating a new issue.
218
+
219
+ Args:
220
+ owner: The owner of the repository.
221
+ repository: The name of the repository.
222
+ title: The title of the issue.
223
+ body: The body content of the issue.
224
+ assignee: The assignee of the issue.
225
+ milestone: The milestone number or title for the issue.
226
+ labels: A list of labels to assign to the issue.
227
+ assignees: A list of assignees for the issue.
228
+ issue_type: The type of the issue.
229
+ **kwargs: Additional arguments for the request.
230
+
231
+ """
232
+ endpoint = self._create_issue_endpoint(owner=owner, repository=repository)
233
+ default_headers = {
234
+ "Accept": "application/vnd.github+json",
235
+ "X-GitHub-Api-Version": "2022-11-28",
236
+ }
237
+ headers = kwargs.get("headers", {})
238
+ headers = {**default_headers, **headers}
239
+ kwargs["headers"] = headers
240
+
241
+ payload: dict[str, str | int | list[str]] = {"title": title}
242
+ if body is not None:
243
+ payload["body"] = body
244
+ if assignee is not None:
245
+ payload["assignee"] = assignee
246
+ if milestone is not None:
247
+ payload["milestone"] = milestone
248
+ if labels is not None:
249
+ payload["labels"] = labels
250
+ if assignees is not None:
251
+ payload["assignees"] = assignees
252
+ if issue_type is not None:
253
+ payload["type"] = issue_type
254
+
255
+ return endpoint, payload, kwargs
256
+
257
+ def _get_issue_endpoint(self, owner: str, repository: str, issue_number: int) -> str:
258
+ """Get the endpoint for a specific issue.
259
+
260
+ Args:
261
+ owner: The owner of the repository.
262
+ repository: The name of the repository.
263
+ issue_number: The number of the issue.
264
+
265
+ Returns:
266
+ The API endpoint for the specific issue.
267
+
268
+ """
269
+ return f"/repos/{owner}/{repository}/issues/{issue_number}"
270
+
271
+ def _get_issue_helper(
272
+ self, owner: str, repository: str, issue_number: int, **kwargs: Any
273
+ ) -> tuple[str, dict[str, Any]]:
274
+ """Prepare the endpoint and arguments for retrieving a specific issue.
275
+
276
+ Args:
277
+ owner: The owner of the repository.
278
+ repository: The name of the repository.
279
+ issue_number: The number of the issue.
280
+ **kwargs: Additional arguments for the request.
281
+
282
+ Returns:
283
+ A tuple containing the endpoint and request arguments.
284
+
285
+ """
286
+ endpoint = self._get_issue_endpoint(owner=owner, repository=repository, issue_number=issue_number)
287
+ default_headers = {
288
+ "Accept": "application/vnd.github+json",
289
+ "X-GitHub-Api-Version": "2022-11-28",
290
+ }
291
+ headers = kwargs.get("headers", {})
292
+ headers = {**default_headers, **headers}
293
+ kwargs["headers"] = headers
294
+
295
+ return endpoint, kwargs
296
+
297
+ def _update_issue_endpoint(self, owner: str, repository: str, issue_number: int) -> str:
298
+ """Get the endpoint for updating a specific issue.
299
+
300
+ Args:
301
+ owner: The owner of the repository.
302
+ repository: The name of the repository.
303
+ issue_number: The number of the issue.
304
+
305
+ Returns:
306
+ The API endpoint for updating the specific issue.
307
+
308
+ """
309
+ return f"/repos/{owner}/{repository}/issues/{issue_number}"
310
+
311
+ def _update_issue_helper( # noqa: PLR0913
312
+ self,
313
+ owner: str,
314
+ repository: str,
315
+ issue_number: int,
316
+ title: str | None = None,
317
+ body: str | None = None,
318
+ assignee: str | None = None,
319
+ state: Literal["open", "closed"] | None = None,
320
+ state_reason: Literal["completed", "not_planned", "duplicate", "reopened", "null"] | None = None,
321
+ milestone: str | int | None = None,
322
+ labels: list[str] | None = None,
323
+ assignees: list[str] | None = None,
324
+ issue_type: str | None = None,
325
+ **kwargs: Any,
326
+ ) -> tuple[str, dict[str, Any], dict[str, Any]]:
327
+ """Prepare the endpoint and payload for updating a specific issue.
328
+
329
+ Args:
330
+ owner: The owner of the repository.
331
+ repository: The name of the repository.
332
+ issue_number: The number of the issue.
333
+ title: The new title of the issue.
334
+ body: The new body content of the issue.
335
+ assignee: The new assignee of the issue.
336
+ state: The new state of the issue.
337
+ state_reason: The reason for the state change.
338
+ milestone: The new milestone number or title for the issue.
339
+ labels: A new list of labels to assign to the issue.
340
+ assignees: A new list of assignees for the issue.
341
+ issue_type: The new type of the issue.
342
+ **kwargs: Additional arguments for the request.
343
+
344
+ Returns:
345
+ A tuple containing the endpoint, payload, and request arguments.
346
+
347
+ """
348
+ endpoint = self._update_issue_endpoint(owner=owner, repository=repository, issue_number=issue_number)
349
+ default_headers = {
350
+ "Accept": "application/vnd.github+json",
351
+ "X-GitHub-Api-Version": "2022-11-28",
352
+ }
353
+ headers = kwargs.get("headers", {})
354
+ headers = {**default_headers, **headers}
355
+ kwargs["headers"] = headers
356
+
357
+ payload: dict[str, Any] = {}
358
+ if title is not None:
359
+ payload["title"] = title
360
+ if body is not None:
361
+ payload["body"] = body
362
+ if assignee is not None:
363
+ payload["assignee"] = assignee
364
+ if state is not None:
365
+ payload["state"] = state
366
+ if state_reason is not None:
367
+ payload["state_reason"] = state_reason
368
+ if milestone is not None:
369
+ payload["milestone"] = milestone
370
+ if labels is not None:
371
+ payload["labels"] = labels
372
+ if assignees is not None:
373
+ payload["assignees"] = assignees
374
+ if issue_type is not None:
375
+ payload["type"] = issue_type
376
+
377
+ return endpoint, payload, kwargs
378
+
379
+ def _lock_issue_endpoint(self, owner: str, repository: str, issue_number: int) -> str:
380
+ """Get the endpoint for locking a specific issue.
381
+
382
+ Args:
383
+ owner: The owner of the repository.
384
+ repository: The name of the repository.
385
+ issue_number: The number of the issue.
386
+
387
+ Returns:
388
+ The API endpoint for locking the specific issue.
389
+
390
+ """
391
+ return f"/repos/{owner}/{repository}/issues/{issue_number}/lock"
392
+
393
+ def _lock_issue_helper(
394
+ self,
395
+ owner: str,
396
+ repository: str,
397
+ issue_number: int,
398
+ lock_reason: Literal["off-topic", "too heated", "resolved", "spam"] | None = None,
399
+ **kwargs: Any,
400
+ ) -> tuple[str, dict[str, Any], dict[str, Any]]:
401
+ """Prepare the endpoint and payload for locking a specific issue.
402
+
403
+ Args:
404
+ owner: The owner of the repository.
405
+ repository: The name of the repository.
406
+ issue_number: The number of the issue.
407
+ lock_reason: The reason for locking the issue.
408
+ **kwargs: Additional arguments for the request.
409
+
410
+ Returns:
411
+ A tuple containing the endpoint, payload, and request arguments.
412
+
413
+ """
414
+ endpoint = self._lock_issue_endpoint(owner, repository, issue_number)
415
+ default_headers = {
416
+ "Accept": "application/vnd.github+json",
417
+ "X-GitHub-Api-Version": "2022-11-28",
418
+ }
419
+ headers = kwargs.get("headers", {})
420
+ headers = {**default_headers, **headers}
421
+ kwargs["headers"] = headers
422
+
423
+ payload = {"lock_reason": lock_reason} if lock_reason is not None else {}
424
+
425
+ return endpoint, payload, kwargs
426
+
427
+ def _unlock_issue_endpoint(self, owner: str, repository: str, issue_number: int) -> str:
428
+ """Get the endpoint for unlocking a specific issue.
429
+
430
+ Args:
431
+ owner: The owner of the repository.
432
+ repository: The name of the repository.
433
+ issue_number: The number of the issue.
434
+
435
+ Returns:
436
+ The API endpoint for unlocking the specific issue.
437
+
438
+ """
439
+ return f"/repos/{owner}/{repository}/issues/{issue_number}/lock"
440
+
441
+ def _unlock_issue_helper(
442
+ self,
443
+ owner: str,
444
+ repository: str,
445
+ issue_number: int,
446
+ **kwargs: Any,
447
+ ) -> tuple[str, dict[str, Any]]:
448
+ """Prepare the endpoint and arguments for unlocking a specific issue.
449
+
450
+ Args:
451
+ owner: The owner of the repository.
452
+ repository: The name of the repository.
453
+ issue_number: The number of the issue.
454
+ **kwargs: Additional arguments for the request.
455
+
456
+ Returns:
457
+ A tuple containing the endpoint and request arguments.
458
+
459
+ """
460
+ endpoint = self._unlock_issue_endpoint(owner, repository, issue_number)
461
+ default_headers = {
462
+ "Accept": "application/vnd.github+json",
463
+ "X-GitHub-Api-Version": "2022-11-28",
464
+ }
465
+ headers = kwargs.get("headers", {})
466
+ headers = {**default_headers, **headers}
467
+ kwargs["headers"] = headers
468
+
469
+ return endpoint, kwargs