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
@@ -0,0 +1,285 @@
1
+ """Asynchronous User Resource for GitHub API."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, cast
6
+
7
+ from aiohttp import ClientResponse
8
+
9
+ from ghnova.resource.async_resource import AsyncResource
10
+ from ghnova.user.base import BaseUser
11
+ from ghnova.utils.response import process_async_response_with_last_modified
12
+
13
+
14
+ class AsyncUser(BaseUser, AsyncResource):
15
+ """GitHub Asynchronous User resource."""
16
+
17
+ async def _get_user(
18
+ self,
19
+ username: str | None = None,
20
+ account_id: int | None = None,
21
+ etag: str | None = None,
22
+ last_modified: str | None = None,
23
+ **kwargs: Any,
24
+ ) -> ClientResponse:
25
+ """Asynchronously get user information.
26
+
27
+ Args:
28
+ username: The username of the user to retrieve. If None, retrieves the authenticated user.
29
+ account_id: The account ID of the user to retrieve. If None, retrieves by username.
30
+ etag: The ETag value for conditional requests.
31
+ last_modified: The Last-Modified timestamp for conditional requests.
32
+ **kwargs: Additional arguments for the request.
33
+
34
+ Returns:
35
+ The ClientResponse object.
36
+
37
+ """
38
+ endpoint, kwargs = self._get_user_helper(username=username, account_id=account_id, **kwargs)
39
+ return await self._get(endpoint=endpoint, etag=etag, last_modified=last_modified, **kwargs)
40
+
41
+ async def get_user(
42
+ self,
43
+ username: str | None = None,
44
+ account_id: int | None = None,
45
+ etag: str | None = None,
46
+ last_modified: str | None = None,
47
+ **kwargs: Any,
48
+ ) -> tuple[dict[str, Any], int, str | None, str | None]:
49
+ """Asynchronously get user information.
50
+
51
+ Args:
52
+ username: The username of the user to retrieve. If None, retrieves the authenticated user.
53
+ account_id: The account ID of the user to retrieve. If None, retrieves by username.
54
+ etag: The ETag value for conditional requests.
55
+ last_modified: The Last-Modified timestamp for conditional requests.
56
+ **kwargs: Additional arguments for the request.
57
+
58
+ Returns:
59
+ A tuple containing:
60
+
61
+ - A dictionary with user information (empty if 304 Not Modified).
62
+ - The HTTP status code.
63
+ - The ETag value from the response headers (if present).
64
+ - The Last-Modified timestamp from the response headers (if present).
65
+
66
+ """
67
+ response = await self._get_user(
68
+ username=username, account_id=account_id, etag=etag, last_modified=last_modified, **kwargs
69
+ )
70
+ data, status_code, etag_value, last_modified_value = await process_async_response_with_last_modified(response)
71
+ data = cast(dict[str, Any], data)
72
+ return data, status_code, etag_value, last_modified_value
73
+
74
+ async def _update_user( # noqa: PLR0913
75
+ self,
76
+ name: str | None = None,
77
+ email: str | None = None,
78
+ blog: str | None = None,
79
+ twitter_username: str | None = None,
80
+ company: str | None = None,
81
+ location: str | None = None,
82
+ hireable: bool | None = None,
83
+ bio: str | None = None,
84
+ etag: str | None = None,
85
+ last_modified: str | None = None,
86
+ **kwargs: Any,
87
+ ) -> ClientResponse:
88
+ """Asynchronously update the authenticated user's information.
89
+
90
+ Args:
91
+ name: The user's name.
92
+ email: The user's email.
93
+ blog: The user's blog URL.
94
+ twitter_username: The user's Twitter username.
95
+ company: The user's company.
96
+ location: The user's location.
97
+ hireable: Whether the user is available for hire.
98
+ bio: The user's bio.
99
+ etag: The ETag value for conditional requests.
100
+ last_modified: The Last-Modified timestamp for conditional requests.
101
+ **kwargs: Additional arguments for the request.
102
+
103
+ Returns:
104
+ The ClientResponse object.
105
+
106
+ """
107
+ endpoint, payload, kwargs = self._update_user_helper(
108
+ name=name,
109
+ email=email,
110
+ blog=blog,
111
+ twitter_username=twitter_username,
112
+ company=company,
113
+ location=location,
114
+ hireable=hireable,
115
+ bio=bio,
116
+ **kwargs,
117
+ )
118
+ return await self._patch(endpoint=endpoint, json=payload, etag=etag, last_modified=last_modified, **kwargs)
119
+
120
+ async def update_user( # noqa: PLR0913
121
+ self,
122
+ name: str | None = None,
123
+ email: str | None = None,
124
+ blog: str | None = None,
125
+ twitter_username: str | None = None,
126
+ company: str | None = None,
127
+ location: str | None = None,
128
+ hireable: bool | None = None,
129
+ bio: str | None = None,
130
+ etag: str | None = None,
131
+ last_modified: str | None = None,
132
+ **kwargs: Any,
133
+ ) -> tuple[dict[str, Any], int, str | None, str | None]:
134
+ """Asynchronously update the authenticated user's information.
135
+
136
+ Args:
137
+ name: The user's name.
138
+ email: The user's email.
139
+ blog: The user's blog URL.
140
+ twitter_username: The user's Twitter username.
141
+ company: The user's company.
142
+ location: The user's location.
143
+ hireable: Whether the user is available for hire.
144
+ bio: The user's bio.
145
+ etag: The ETag value for conditional requests.
146
+ last_modified: The Last-Modified timestamp for conditional requests.
147
+ **kwargs: Additional arguments for the request.
148
+
149
+ Returns:
150
+ A tuple containing:
151
+
152
+ - A dictionary with updated user information.
153
+ - The HTTP status code.
154
+ - The ETag value from the response headers (if present).
155
+ - The Last-Modified timestamp from the response headers (if present).
156
+
157
+ """
158
+ response = await self._update_user(
159
+ name=name,
160
+ email=email,
161
+ blog=blog,
162
+ twitter_username=twitter_username,
163
+ company=company,
164
+ location=location,
165
+ hireable=hireable,
166
+ bio=bio,
167
+ etag=etag,
168
+ last_modified=last_modified,
169
+ **kwargs,
170
+ )
171
+ data, status_code, etag, last_modified = await process_async_response_with_last_modified(response)
172
+ data = cast(dict[str, Any], data)
173
+ return data, status_code, etag, last_modified
174
+
175
+ async def _list_users(
176
+ self,
177
+ since: int | None = None,
178
+ per_page: int | None = None,
179
+ etag: str | None = None,
180
+ last_modified: str | None = None,
181
+ **kwargs: Any,
182
+ ) -> ClientResponse:
183
+ """Asynchronously list all users.
184
+
185
+ Args:
186
+ since: The integer ID of the last User that you've seen.
187
+ per_page: The number of results per page (max 100).
188
+ etag: The ETag value for conditional requests.
189
+ last_modified: The Last-Modified timestamp for conditional requests.
190
+ **kwargs: Additional arguments for the request.
191
+
192
+ Returns:
193
+ The ClientResponse object.
194
+
195
+ """
196
+ endpoint, params, kwargs = self._list_users_helper(since=since, per_page=per_page, **kwargs)
197
+ return await self._get(endpoint=endpoint, params=params, etag=etag, last_modified=last_modified, **kwargs)
198
+
199
+ async def list_users(
200
+ self,
201
+ since: int | None = None,
202
+ per_page: int | None = None,
203
+ etag: str | None = None,
204
+ last_modified: str | None = None,
205
+ **kwargs: Any,
206
+ ) -> tuple[list[dict[str, Any]], int, str | None, str | None]:
207
+ """Asynchronously list all users.
208
+
209
+ Args:
210
+ since: The integer ID of the last User that you've seen.
211
+ per_page: The number of results per page (max 100).
212
+ etag: The ETag value for conditional requests.
213
+ last_modified: The Last-Modified timestamp for conditional requests.
214
+ **kwargs: Additional arguments for the request.
215
+
216
+ Returns:
217
+ A tuple containing:
218
+
219
+ - A list of user dictionaries (empty if 304 Not Modified).
220
+ - The HTTP status code.
221
+ - The ETag value from the response headers (if present).
222
+ - The Last-Modified timestamp from the response headers (if present).
223
+
224
+ """
225
+ response = await self._list_users(
226
+ since=since, per_page=per_page, etag=etag, last_modified=last_modified, **kwargs
227
+ )
228
+ data, status_code, etag_value, last_modified_value = await process_async_response_with_last_modified(response)
229
+ data = cast(list[dict[str, Any]], data)
230
+ return data, status_code, etag_value, last_modified_value
231
+
232
+ async def _get_contextual_information(
233
+ self,
234
+ username: str,
235
+ subject_type: str | None = None,
236
+ subject_id: str | None = None,
237
+ **kwargs: Any,
238
+ ) -> ClientResponse:
239
+ """Asynchronously get contextual information about a user.
240
+
241
+ Args:
242
+ username: The username of the user.
243
+ subject_type: The type of subject for the hovercard.
244
+ subject_id: The ID of the subject for the hovercard.
245
+ **kwargs: Additional arguments for the request.
246
+
247
+ Returns:
248
+ The ClientResponse object.
249
+
250
+ """
251
+ endpoint, params, kwargs = self._get_contextual_information_helper(
252
+ username=username, subject_type=subject_type, subject_id=subject_id, **kwargs
253
+ )
254
+ return await self._get(endpoint=endpoint, params=params, **kwargs)
255
+
256
+ async def get_contextual_information(
257
+ self,
258
+ username: str,
259
+ subject_type: str | None = None,
260
+ subject_id: str | None = None,
261
+ **kwargs: Any,
262
+ ) -> tuple[dict[str, Any], int, str | None, str | None]:
263
+ """Asynchronously get contextual information about a user.
264
+
265
+ Args:
266
+ username: The username of the user.
267
+ subject_type: The type of subject for the hovercard.
268
+ subject_id: The ID of the subject for the hovercard.
269
+ **kwargs: Additional arguments for the request.
270
+
271
+ Returns:
272
+ A tuple containing:
273
+
274
+ - A dictionary with contextual information.
275
+ - The HTTP status code.
276
+ - The ETag value from the response headers (if present).
277
+ - The Last-Modified timestamp from the response headers (if present).
278
+
279
+ """
280
+ response = await self._get_contextual_information(
281
+ username=username, subject_type=subject_type, subject_id=subject_id, **kwargs
282
+ )
283
+ data, status_code, etag_value, last_modified_value = await process_async_response_with_last_modified(response)
284
+ data = cast(dict[str, Any], data)
285
+ return data, status_code, etag_value, last_modified_value
ghnova/user/base.py ADDED
@@ -0,0 +1,214 @@
1
+ """Base class for GitHub User resource."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+
8
+ class BaseUser:
9
+ """Base class for GitHub User resource."""
10
+
11
+ def _get_user_endpoint(self, username: str | None, account_id: int | None) -> str:
12
+ """Determine the user endpoint based on username or account ID.
13
+
14
+ Args:
15
+ username: The username of the user.
16
+ account_id: The account ID of the user.
17
+
18
+ Returns:
19
+ The API endpoint for the user.
20
+
21
+ """
22
+ if username is None and account_id is None:
23
+ return "/user"
24
+ elif username is not None and account_id is None:
25
+ return f"/users/{username}"
26
+ elif username is None and account_id is not None:
27
+ return f"/user/{account_id}"
28
+ else:
29
+ raise ValueError("Specify either username or account_id, not both.")
30
+
31
+ def _get_user_helper(
32
+ self, username: str | None = None, account_id: int | None = None, **kwargs: Any
33
+ ) -> tuple[str, dict[str, Any]]:
34
+ """Get user information.
35
+
36
+ Args:
37
+ username: The username of the user to retrieve. If None, retrieves the authenticated user.
38
+ account_id: The account ID of the user to retrieve. If None, retrieves by username.
39
+ **kwargs: Additional arguments for the request.
40
+
41
+ Returns:
42
+ A tuple containing the endpoint and the request arguments.
43
+ - The API endpoint for the user.
44
+ - A dictionary of request arguments.
45
+
46
+ """
47
+ endpoint = self._get_user_endpoint(username=username, account_id=account_id)
48
+ default_headers = {
49
+ "Accept": "application/vnd.github+json",
50
+ "X-GitHub-Api-Version": "2022-11-28",
51
+ }
52
+ headers = kwargs.get("headers", {})
53
+ headers = {**default_headers, **headers}
54
+ kwargs["headers"] = headers
55
+
56
+ return endpoint, kwargs
57
+
58
+ def _update_user_endpoint(self) -> str:
59
+ """Get the endpoint for updating the authenticated user.
60
+
61
+ Returns:
62
+ The API endpoint for updating the authenticated user.
63
+
64
+ """
65
+ return "/user"
66
+
67
+ def _update_user_helper( # noqa: PLR0913
68
+ self,
69
+ name: str | None,
70
+ email: str | None,
71
+ blog: str | None,
72
+ twitter_username: str | None,
73
+ company: str | None,
74
+ location: str | None,
75
+ hireable: bool | None,
76
+ bio: str | None,
77
+ **kwargs: Any,
78
+ ) -> tuple[str, dict[str, Any], dict[str, Any]]:
79
+ """Get the endpoint and arguments for updating the authenticated user.
80
+
81
+ Args:
82
+ name: The name of the user.
83
+ email: The email of the user.
84
+ blog: The blog URL of the user.
85
+ twitter_username: The Twitter username of the user.
86
+ company: The company of the user.
87
+ location: The location of the user.
88
+ hireable: The hirable status of the user.
89
+ bio: The bio of the user.
90
+ **kwargs: Additional arguments for the request.
91
+
92
+ Returns:
93
+ A tuple containing the endpoint and the request arguments.
94
+ - The API endpoint for updating the authenticated user.
95
+ - A dictionary representing the JSON payload.
96
+ - A dictionary of request arguments.
97
+
98
+ """
99
+ endpoint = self._update_user_endpoint()
100
+ default_headers = {
101
+ "Accept": "application/vnd.github+json",
102
+ "X-GitHub-Api-Version": "2022-11-28",
103
+ }
104
+ headers = kwargs.get("headers", {})
105
+ headers = {**default_headers, **headers}
106
+ kwargs["headers"] = headers
107
+
108
+ payload = {}
109
+ if name is not None:
110
+ payload["name"] = name
111
+ if email is not None:
112
+ payload["email"] = email
113
+ if blog is not None:
114
+ payload["blog"] = blog
115
+ if twitter_username is not None:
116
+ payload["twitter_username"] = twitter_username
117
+ if company is not None:
118
+ payload["company"] = company
119
+ if location is not None:
120
+ payload["location"] = location
121
+
122
+ if hireable is not None:
123
+ payload["hireable"] = hireable
124
+ if bio is not None:
125
+ payload["bio"] = bio
126
+
127
+ return endpoint, payload, kwargs
128
+
129
+ def _list_users_endpoint(self) -> str:
130
+ """Get the endpoint for listing all users.
131
+
132
+ Returns:
133
+ The API endpoint for listing all users.
134
+
135
+ """
136
+ return "/users"
137
+
138
+ def _list_users_helper(
139
+ self, since: int | None, per_page: int | None, **kwargs: Any
140
+ ) -> tuple[str, dict[str, int], dict[str, Any]]:
141
+ """Get the endpoint and arguments for listing all users.
142
+
143
+ Args:
144
+ since: The user ID to start from.
145
+ per_page: The number of users per page.
146
+ **kwargs: Additional arguments for the request.
147
+
148
+ Returns:
149
+ A tuple containing the endpoint and the request arguments.
150
+ - The API endpoint for listing all users.
151
+ - A dictionary of query parameters.
152
+ - A dictionary of request arguments.
153
+
154
+ """
155
+ endpoint = self._list_users_endpoint()
156
+ default_headers = {
157
+ "Accept": "application/vnd.github+json",
158
+ "X-GitHub-Api-Version": "2022-11-28",
159
+ }
160
+ headers = kwargs.get("headers", {})
161
+ headers = {**default_headers, **headers}
162
+ kwargs["headers"] = headers
163
+
164
+ params = {}
165
+ if since is not None:
166
+ params["since"] = since
167
+ if per_page is not None:
168
+ params["per_page"] = per_page
169
+
170
+ return endpoint, params, kwargs
171
+
172
+ def _get_contextual_information_endpoint(self) -> str:
173
+ """Get the endpoint for retrieving contextual information about the authenticated user.
174
+
175
+ Returns:
176
+ The API endpoint for retrieving contextual information.
177
+
178
+ """
179
+ return "/users/{username}/hovercard"
180
+
181
+ def _get_contextual_information_helper(
182
+ self, username: str, subject_type: str | None = None, subject_id: str | None = None, **kwargs: Any
183
+ ) -> tuple[str, dict[str, str], dict[str, Any]]:
184
+ """Get the endpoint and arguments for retrieving contextual information about a user.
185
+
186
+ Args:
187
+ username: The username of the user.
188
+ subject_type: The type of subject for the hovercard.
189
+ subject_id: The ID of the subject for the hovercard.
190
+ **kwargs: Additional arguments for the request.
191
+
192
+ Returns:
193
+ A tuple containing the endpoint and the request arguments.
194
+ - The API endpoint for retrieving contextual information.
195
+ - A dictionary of query parameters.
196
+ - A dictionary of request arguments.
197
+
198
+ """
199
+ endpoint = self._get_contextual_information_endpoint().format(username=username)
200
+ default_headers = {
201
+ "Accept": "application/vnd.github+json",
202
+ "X-GitHub-Api-Version": "2022-11-28",
203
+ }
204
+ headers = kwargs.get("headers", {})
205
+ headers = {**default_headers, **headers}
206
+ kwargs["headers"] = headers
207
+
208
+ params = {}
209
+ if subject_type is not None:
210
+ params["subject_type"] = subject_type
211
+ if subject_id is not None:
212
+ params["subject_id"] = subject_id
213
+
214
+ return endpoint, params, kwargs