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,267 @@
1
+ """List command for issue CLI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from datetime import datetime
6
+ from typing import Annotated, Literal
7
+
8
+ import typer
9
+
10
+
11
+ def list_command( # noqa: PLR0913
12
+ ctx: typer.Context,
13
+ account_name: Annotated[
14
+ str | None,
15
+ typer.Option(
16
+ "--account-name",
17
+ help="Name of the account to use for authentication.",
18
+ ),
19
+ ] = None,
20
+ token: Annotated[
21
+ str | None,
22
+ typer.Option(
23
+ "--token",
24
+ help="Token for authentication. If not provided, the token from the specified account will be used.",
25
+ ),
26
+ ] = None,
27
+ base_url: Annotated[
28
+ str | None,
29
+ typer.Option(
30
+ "--base-url",
31
+ help="Base URL of the GitHub platform. If not provided, the base URL from the specified account will be used.",
32
+ ),
33
+ ] = None,
34
+ owner: Annotated[
35
+ str | None,
36
+ typer.Option(
37
+ "--owner",
38
+ help="The owner of the repository.",
39
+ ),
40
+ ] = None,
41
+ organization: Annotated[
42
+ str | None,
43
+ typer.Option(
44
+ "--organization",
45
+ help="The organization name.",
46
+ ),
47
+ ] = None,
48
+ repository: Annotated[
49
+ str | None,
50
+ typer.Option(
51
+ "--repository",
52
+ help="The name of the repository.",
53
+ ),
54
+ ] = None,
55
+ filter_by: Annotated[
56
+ Literal["assigned", "created", "mentioned", "subscribed", "all"] | None,
57
+ typer.Option(
58
+ "--filter-by",
59
+ help="Filter issues by: assigned, created, mentioned, subscribed, or all.",
60
+ ),
61
+ ] = None,
62
+ state: Annotated[
63
+ Literal["open", "closed", "all"] | None,
64
+ typer.Option(
65
+ "--state",
66
+ help="Filter by state: open, closed, or all.",
67
+ ),
68
+ ] = None,
69
+ labels: Annotated[list[str] | None, typer.Option("--labels", help="Filter by labels.")] = None,
70
+ sort: Annotated[
71
+ Literal["created", "updated", "comments"] | None,
72
+ typer.Option(
73
+ "--sort",
74
+ help="Sort by: created, updated, or comments.",
75
+ ),
76
+ ] = None,
77
+ direction: Annotated[
78
+ Literal["asc", "desc"] | None,
79
+ typer.Option(
80
+ "--direction",
81
+ help="Sort direction: asc or desc.",
82
+ ),
83
+ ] = None,
84
+ since: Annotated[
85
+ datetime | None,
86
+ typer.Option(
87
+ "--since",
88
+ help="Only issues updated at or after this time are returned (ISO 8601 format).",
89
+ ),
90
+ ] = None,
91
+ collab: Annotated[
92
+ bool | None,
93
+ typer.Option(
94
+ "--collab/--no-collab",
95
+ help="Include issues the user is a collaborator on.",
96
+ ),
97
+ ] = None,
98
+ orgs: Annotated[
99
+ bool | None,
100
+ typer.Option(
101
+ "--orgs/--no-orgs",
102
+ help="Include issues from organizations the user is a member of.",
103
+ ),
104
+ ] = None,
105
+ owned: Annotated[
106
+ bool | None,
107
+ typer.Option(
108
+ "--owned/--no-owned",
109
+ help="Include issues owned by the authenticated user.",
110
+ ),
111
+ ] = None,
112
+ pulls: Annotated[
113
+ bool | None,
114
+ typer.Option(
115
+ "--pulls/--no-pulls",
116
+ help="Include pull requests in the results.",
117
+ ),
118
+ ] = None,
119
+ issue_type: Annotated[
120
+ str | None,
121
+ typer.Option(
122
+ "--issue-type",
123
+ help="Filter by issue type.",
124
+ ),
125
+ ] = None,
126
+ milestone: Annotated[
127
+ str | None,
128
+ typer.Option(
129
+ "--milestone",
130
+ help="Filter by milestone.",
131
+ ),
132
+ ] = None,
133
+ assignee: Annotated[
134
+ str | None,
135
+ typer.Option(
136
+ "--assignee",
137
+ help="Filter by assignee.",
138
+ ),
139
+ ] = None,
140
+ creator: Annotated[
141
+ str | None,
142
+ typer.Option(
143
+ "--creator",
144
+ help="Filter by creator.",
145
+ ),
146
+ ] = None,
147
+ mentioned: Annotated[
148
+ str | None,
149
+ typer.Option(
150
+ "--mentioned",
151
+ help="Filter by mentioned user.",
152
+ ),
153
+ ] = None,
154
+ per_page: Annotated[
155
+ int,
156
+ typer.Option(
157
+ "--per-page",
158
+ help="Number of results per page.",
159
+ ),
160
+ ] = 30,
161
+ page: Annotated[
162
+ int,
163
+ typer.Option(
164
+ "--page",
165
+ help="Page number for pagination.",
166
+ ),
167
+ ] = 1,
168
+ etag: Annotated[
169
+ str | None,
170
+ typer.Option(
171
+ "--etag",
172
+ help="ETag from a previous request for caching purposes.",
173
+ ),
174
+ ] = None,
175
+ last_modified: Annotated[
176
+ str | None,
177
+ typer.Option(
178
+ "--last-modified",
179
+ help="Last-Modified header from a previous request for caching purposes.",
180
+ ),
181
+ ] = None,
182
+ ) -> None:
183
+ """List issues from a repository or organization.
184
+
185
+ Args:
186
+ ctx: Typer context.
187
+ account_name: Name of the account to use for authentication.
188
+ token: Token for authentication.
189
+ base_url: Base URL of the GitHub platform.
190
+ owner: The owner of the repository.
191
+ organization: The organization name.
192
+ repository: The name of the repository.
193
+ filter_by: Filter issues by: assigned, created, mentioned, subscribed, or all.
194
+ state: Filter by state: open, closed, or all.
195
+ labels: Filter by labels.
196
+ sort: Sort by: created, updated, or comments.
197
+ direction: Sort direction: asc or desc.
198
+ since: Only issues updated at or after this time are returned.
199
+ collab: Include issues the user is a collaborator on.
200
+ orgs: Include issues from organizations the user is a member of.
201
+ owned: Include issues owned by the authenticated user.
202
+ pulls: Include pull requests in the results.
203
+ issue_type: Filter by issue type.
204
+ milestone: Filter by milestone.
205
+ assignee: Filter by assignee.
206
+ creator: Filter by creator.
207
+ mentioned: Filter by mentioned user.
208
+ per_page: Number of results per page.
209
+ page: Page number for pagination.
210
+ etag: ETag from a previous request for caching purposes.
211
+ last_modified: Last-Modified header from a previous request for caching purposes.
212
+
213
+ """
214
+ import json # noqa: PLC0415
215
+ import logging # noqa: PLC0415
216
+
217
+ from ghnova.cli.utils.auth import get_auth_params # noqa: PLC0415
218
+ from ghnova.client.github import GitHub # noqa: PLC0415
219
+
220
+ logger = logging.getLogger("ghnova")
221
+
222
+ token, base_url = get_auth_params(
223
+ config_path=ctx.obj["config_path"],
224
+ account_name=account_name,
225
+ token=token,
226
+ base_url=base_url,
227
+ )
228
+
229
+ try:
230
+ with GitHub(token=token, base_url=base_url) as client:
231
+ issue_client = client.issue
232
+ data, status_code, etag_value, last_modified_value = issue_client.list_issues(
233
+ owner=owner,
234
+ organization=organization,
235
+ repository=repository,
236
+ filter_by=filter_by,
237
+ state=state,
238
+ labels=labels,
239
+ sort=sort,
240
+ direction=direction,
241
+ since=since,
242
+ collab=collab,
243
+ orgs=orgs,
244
+ owned=owned,
245
+ pulls=pulls,
246
+ issue_type=issue_type,
247
+ milestone=milestone,
248
+ assignee=assignee,
249
+ creator=creator,
250
+ mentioned=mentioned,
251
+ per_page=per_page,
252
+ page=page,
253
+ etag=etag,
254
+ last_modified=last_modified,
255
+ )
256
+ result = {
257
+ "data": data,
258
+ "metadata": {
259
+ "status_code": status_code,
260
+ "etag": etag_value,
261
+ "last_modified": last_modified_value,
262
+ },
263
+ }
264
+ print(json.dumps(result, indent=2, default=str))
265
+ except Exception as e:
266
+ logger.error("Error listing issues: %s", e)
267
+ raise typer.Exit(code=1) from e
@@ -0,0 +1,110 @@
1
+ """Lock command for issue CLI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Annotated, Literal
6
+
7
+ import typer
8
+
9
+
10
+ def lock_command( # noqa: PLR0913
11
+ ctx: typer.Context,
12
+ owner: Annotated[
13
+ str,
14
+ typer.Option(
15
+ "--owner",
16
+ help="The owner of the repository.",
17
+ ),
18
+ ],
19
+ repository: Annotated[
20
+ str,
21
+ typer.Option(
22
+ "--repository",
23
+ help="The name of the repository.",
24
+ ),
25
+ ],
26
+ issue_number: Annotated[
27
+ int,
28
+ typer.Option(
29
+ "--issue-number",
30
+ help="The issue number.",
31
+ ),
32
+ ],
33
+ account_name: Annotated[
34
+ str | None,
35
+ typer.Option(
36
+ "--account-name",
37
+ help="Name of the account to use for authentication.",
38
+ ),
39
+ ] = None,
40
+ token: Annotated[
41
+ str | None,
42
+ typer.Option(
43
+ "--token",
44
+ help="Token for authentication. If not provided, the token from the specified account will be used.",
45
+ ),
46
+ ] = None,
47
+ base_url: Annotated[
48
+ str | None,
49
+ typer.Option(
50
+ "--base-url",
51
+ help="Base URL of the GitHub platform. If not provided, the base URL from the specified account will be used.",
52
+ ),
53
+ ] = None,
54
+ lock_reason: Annotated[
55
+ Literal["off-topic", "too heated", "resolved", "spam"] | None,
56
+ typer.Option(
57
+ "--lock-reason",
58
+ help="Reason for locking: off-topic, too heated, resolved, or spam.",
59
+ ),
60
+ ] = None,
61
+ ) -> None:
62
+ """Lock an issue to prevent further comments.
63
+
64
+ Args:
65
+ ctx: Typer context.
66
+ owner: The owner of the repository.
67
+ repository: The name of the repository.
68
+ issue_number: The issue number.
69
+ account_name: Name of the account to use for authentication.
70
+ token: Token for authentication.
71
+ base_url: Base URL of the GitHub platform.
72
+ lock_reason: Reason for locking the issue.
73
+
74
+ """
75
+ import json # noqa: PLC0415
76
+ import logging # noqa: PLC0415
77
+
78
+ from ghnova.cli.utils.auth import get_auth_params # noqa: PLC0415
79
+ from ghnova.client.github import GitHub # noqa: PLC0415
80
+
81
+ logger = logging.getLogger("ghnova")
82
+
83
+ token, base_url = get_auth_params(
84
+ config_path=ctx.obj["config_path"],
85
+ account_name=account_name,
86
+ token=token,
87
+ base_url=base_url,
88
+ )
89
+
90
+ try:
91
+ with GitHub(token=token, base_url=base_url) as client:
92
+ issue_client = client.issue
93
+ data, status_code, etag_value, last_modified_value = issue_client.lock_issue(
94
+ owner=owner,
95
+ repository=repository,
96
+ issue_number=issue_number,
97
+ lock_reason=lock_reason,
98
+ )
99
+ result = {
100
+ "data": data,
101
+ "metadata": {
102
+ "status_code": status_code,
103
+ "etag": etag_value,
104
+ "last_modified": last_modified_value,
105
+ },
106
+ }
107
+ print(json.dumps(result, indent=2, default=str))
108
+ except Exception as e:
109
+ logger.error("Error locking issue: %s", e)
110
+ raise typer.Exit(code=1) from e
@@ -0,0 +1,31 @@
1
+ """Issue CLI commands for ghnova."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import typer
6
+
7
+ issue_app = typer.Typer(
8
+ name="issue",
9
+ help="Manage GitHub issues.",
10
+ rich_markup_mode="rich",
11
+ )
12
+
13
+
14
+ def register_commands() -> None:
15
+ """Register issue subcommands."""
16
+ from ghnova.cli.issue.create import create_command # noqa: PLC0415
17
+ from ghnova.cli.issue.get import get_command # noqa: PLC0415
18
+ from ghnova.cli.issue.list import list_command # noqa: PLC0415
19
+ from ghnova.cli.issue.lock import lock_command # noqa: PLC0415
20
+ from ghnova.cli.issue.unlock import unlock_command # noqa: PLC0415
21
+ from ghnova.cli.issue.update import update_command # noqa: PLC0415
22
+
23
+ issue_app.command(name="create", help="Create a new issue.")(create_command)
24
+ issue_app.command(name="get", help="Get a specific issue.")(get_command)
25
+ issue_app.command(name="list", help="List issues.")(list_command)
26
+ issue_app.command(name="lock", help="Lock an issue.")(lock_command)
27
+ issue_app.command(name="unlock", help="Unlock an issue.")(unlock_command)
28
+ issue_app.command(name="update", help="Update an issue.")(update_command)
29
+
30
+
31
+ register_commands()
@@ -0,0 +1,101 @@
1
+ """Unlock command for issue CLI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Annotated
6
+
7
+ import typer
8
+
9
+
10
+ def unlock_command( # noqa: PLR0913
11
+ ctx: typer.Context,
12
+ owner: Annotated[
13
+ str,
14
+ typer.Option(
15
+ "--owner",
16
+ help="The owner of the repository.",
17
+ ),
18
+ ],
19
+ repository: Annotated[
20
+ str,
21
+ typer.Option(
22
+ "--repository",
23
+ help="The name of the repository.",
24
+ ),
25
+ ],
26
+ issue_number: Annotated[
27
+ int,
28
+ typer.Option(
29
+ "--issue-number",
30
+ help="The issue number.",
31
+ ),
32
+ ],
33
+ account_name: Annotated[
34
+ str | None,
35
+ typer.Option(
36
+ "--account-name",
37
+ help="Name of the account to use for authentication.",
38
+ ),
39
+ ] = None,
40
+ token: Annotated[
41
+ str | None,
42
+ typer.Option(
43
+ "--token",
44
+ help="Token for authentication. If not provided, the token from the specified account will be used.",
45
+ ),
46
+ ] = None,
47
+ base_url: Annotated[
48
+ str | None,
49
+ typer.Option(
50
+ "--base-url",
51
+ help="Base URL of the GitHub platform. If not provided, the base URL from the specified account will be used.",
52
+ ),
53
+ ] = None,
54
+ ) -> None:
55
+ """Unlock an issue to allow further comments.
56
+
57
+ Args:
58
+ ctx: Typer context.
59
+ owner: The owner of the repository.
60
+ repository: The name of the repository.
61
+ issue_number: The issue number.
62
+ account_name: Name of the account to use for authentication.
63
+ token: Token for authentication.
64
+ base_url: Base URL of the GitHub platform.
65
+
66
+ """
67
+ import json # noqa: PLC0415
68
+ import logging # noqa: PLC0415
69
+
70
+ from ghnova.cli.utils.auth import get_auth_params # noqa: PLC0415
71
+ from ghnova.client.github import GitHub # noqa: PLC0415
72
+
73
+ logger = logging.getLogger("ghnova")
74
+
75
+ token, base_url = get_auth_params(
76
+ config_path=ctx.obj["config_path"],
77
+ account_name=account_name,
78
+ token=token,
79
+ base_url=base_url,
80
+ )
81
+
82
+ try:
83
+ with GitHub(token=token, base_url=base_url) as client:
84
+ issue_client = client.issue
85
+ data, status_code, etag_value, last_modified_value = issue_client.unlock_issue(
86
+ owner=owner,
87
+ repository=repository,
88
+ issue_number=issue_number,
89
+ )
90
+ result = {
91
+ "data": data,
92
+ "metadata": {
93
+ "status_code": status_code,
94
+ "etag": etag_value,
95
+ "last_modified": last_modified_value,
96
+ },
97
+ }
98
+ print(json.dumps(result, indent=2, default=str))
99
+ except Exception as e:
100
+ logger.error("Error unlocking issue: %s", e)
101
+ raise typer.Exit(code=1) from e
@@ -0,0 +1,164 @@
1
+ """Update command for issue CLI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Annotated, Literal
6
+
7
+ import typer
8
+
9
+
10
+ def update_command( # noqa: PLR0913
11
+ ctx: typer.Context,
12
+ owner: Annotated[
13
+ str,
14
+ typer.Option(
15
+ "--owner",
16
+ help="The owner of the repository.",
17
+ ),
18
+ ],
19
+ repository: Annotated[
20
+ str,
21
+ typer.Option(
22
+ "--repository",
23
+ help="The name of the repository.",
24
+ ),
25
+ ],
26
+ issue_number: Annotated[
27
+ int,
28
+ typer.Option(
29
+ "--issue-number",
30
+ help="The issue number.",
31
+ ),
32
+ ],
33
+ account_name: Annotated[
34
+ str | None,
35
+ typer.Option(
36
+ "--account-name",
37
+ help="Name of the account to use for authentication.",
38
+ ),
39
+ ] = None,
40
+ token: Annotated[
41
+ str | None,
42
+ typer.Option(
43
+ "--token",
44
+ help="Token for authentication. If not provided, the token from the specified account will be used.",
45
+ ),
46
+ ] = None,
47
+ base_url: Annotated[
48
+ str | None,
49
+ typer.Option(
50
+ "--base-url",
51
+ help="Base URL of the GitHub platform. If not provided, the base URL from the specified account will be used.",
52
+ ),
53
+ ] = None,
54
+ title: Annotated[
55
+ str | None,
56
+ typer.Option(
57
+ "--title",
58
+ help="The new title of the issue.",
59
+ ),
60
+ ] = None,
61
+ body: Annotated[
62
+ str | None,
63
+ typer.Option(
64
+ "--body",
65
+ help="The new body of the issue.",
66
+ ),
67
+ ] = None,
68
+ assignee: Annotated[
69
+ str | None,
70
+ typer.Option(
71
+ "--assignee",
72
+ help="The assignee of the issue.",
73
+ ),
74
+ ] = None,
75
+ milestone: Annotated[
76
+ str | None,
77
+ typer.Option(
78
+ "--milestone",
79
+ help="The milestone for the issue.",
80
+ ),
81
+ ] = None,
82
+ labels: Annotated[
83
+ list[str] | None,
84
+ typer.Option(
85
+ "--labels",
86
+ help="A new list of labels to assign to the issue.",
87
+ ),
88
+ ] = None,
89
+ assignees: Annotated[
90
+ list[str] | None,
91
+ typer.Option(
92
+ "--assignees",
93
+ help="A new list of assignees for the issue.",
94
+ ),
95
+ ] = None,
96
+ state: Annotated[
97
+ Literal["open", "closed"] | None,
98
+ typer.Option(
99
+ "--state",
100
+ help="The state of the issue (open or closed).",
101
+ ),
102
+ ] = None,
103
+ ) -> None:
104
+ """Update an existing issue.
105
+
106
+ Args:
107
+ ctx: Typer context.
108
+ owner: The owner of the repository.
109
+ repository: The name of the repository.
110
+ issue_number: The issue number.
111
+ account_name: Name of the account to use for authentication.
112
+ token: Token for authentication.
113
+ base_url: Base URL of the GitHub platform.
114
+ title: The new title of the issue.
115
+ body: The new body of the issue.
116
+ assignee: The assignee of the issue.
117
+ milestone: The milestone for the issue.
118
+ labels: A new list of labels to assign to the issue.
119
+ assignees: A new list of assignees for the issue.
120
+ state: The state of the issue (open or closed).
121
+
122
+ """
123
+ import json # noqa: PLC0415
124
+ import logging # noqa: PLC0415
125
+
126
+ from ghnova.cli.utils.auth import get_auth_params # noqa: PLC0415
127
+ from ghnova.client.github import GitHub # noqa: PLC0415
128
+
129
+ logger = logging.getLogger("ghnova")
130
+
131
+ token, base_url = get_auth_params(
132
+ config_path=ctx.obj["config_path"],
133
+ account_name=account_name,
134
+ token=token,
135
+ base_url=base_url,
136
+ )
137
+
138
+ try:
139
+ with GitHub(token=token, base_url=base_url) as client:
140
+ issue_client = client.issue
141
+ data, status_code, etag_value, last_modified_value = issue_client.update_issue(
142
+ owner=owner,
143
+ repository=repository,
144
+ issue_number=issue_number,
145
+ title=title,
146
+ body=body,
147
+ assignee=assignee,
148
+ milestone=milestone,
149
+ labels=labels,
150
+ assignees=assignees,
151
+ state=state,
152
+ )
153
+ result = {
154
+ "data": data,
155
+ "metadata": {
156
+ "status_code": status_code,
157
+ "etag": etag_value,
158
+ "last_modified": last_modified_value,
159
+ },
160
+ }
161
+ print(json.dumps(result, indent=2, default=str))
162
+ except Exception as e:
163
+ logger.error("Error updating issue: %s", e)
164
+ raise typer.Exit(code=1) from e