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.
- ghnova/__init__.py +8 -0
- ghnova/__main__.py +8 -0
- ghnova/cli/__init__.py +1 -0
- ghnova/cli/config/__init__.py +1 -0
- ghnova/cli/config/add.py +48 -0
- ghnova/cli/config/delete.py +50 -0
- ghnova/cli/config/list.py +40 -0
- ghnova/cli/config/main.py +27 -0
- ghnova/cli/config/update.py +59 -0
- ghnova/cli/issue/__init__.py +7 -0
- ghnova/cli/issue/create.py +155 -0
- ghnova/cli/issue/get.py +119 -0
- ghnova/cli/issue/list.py +267 -0
- ghnova/cli/issue/lock.py +110 -0
- ghnova/cli/issue/main.py +31 -0
- ghnova/cli/issue/unlock.py +101 -0
- ghnova/cli/issue/update.py +164 -0
- ghnova/cli/main.py +117 -0
- ghnova/cli/repository/__init__.py +1 -0
- ghnova/cli/repository/list.py +201 -0
- ghnova/cli/repository/main.py +21 -0
- ghnova/cli/user/__init__.py +1 -0
- ghnova/cli/user/ctx_info.py +105 -0
- ghnova/cli/user/get.py +98 -0
- ghnova/cli/user/list.py +78 -0
- ghnova/cli/user/main.py +27 -0
- ghnova/cli/user/update.py +164 -0
- ghnova/cli/utils/__init__.py +7 -0
- ghnova/cli/utils/auth.py +67 -0
- ghnova/client/__init__.py +8 -0
- ghnova/client/async_github.py +121 -0
- ghnova/client/base.py +78 -0
- ghnova/client/github.py +107 -0
- ghnova/config/__init__.py +8 -0
- ghnova/config/manager.py +209 -0
- ghnova/config/model.py +58 -0
- ghnova/issue/__init__.py +8 -0
- ghnova/issue/async_issue.py +554 -0
- ghnova/issue/base.py +469 -0
- ghnova/issue/issue.py +584 -0
- ghnova/repository/__init__.py +8 -0
- ghnova/repository/async_repository.py +134 -0
- ghnova/repository/base.py +124 -0
- ghnova/repository/repository.py +134 -0
- ghnova/resource/__init__.py +8 -0
- ghnova/resource/async_resource.py +88 -0
- ghnova/resource/resource.py +88 -0
- ghnova/user/__init__.py +8 -0
- ghnova/user/async_user.py +285 -0
- ghnova/user/base.py +214 -0
- ghnova/user/user.py +285 -0
- ghnova/utils/__init__.py +16 -0
- ghnova/utils/log.py +70 -0
- ghnova/utils/response.py +67 -0
- ghnova/version.py +11 -0
- ghnova-0.3.0.dist-info/METADATA +194 -0
- ghnova-0.3.0.dist-info/RECORD +60 -0
- ghnova-0.3.0.dist-info/WHEEL +4 -0
- ghnova-0.3.0.dist-info/entry_points.txt +2 -0
- ghnova-0.3.0.dist-info/licenses/LICENSE +21 -0
ghnova/cli/main.py
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# ruff: noqa PL0415
|
|
2
|
+
"""Main entry point for the ghnova CLI application."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import enum
|
|
7
|
+
from typing import Annotated
|
|
8
|
+
|
|
9
|
+
import typer
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class LoggingLevel(str, enum.Enum):
|
|
13
|
+
"""Logging levels for the CLI."""
|
|
14
|
+
|
|
15
|
+
NOTSET = "NOTSET"
|
|
16
|
+
DEBUG = "DEBUG"
|
|
17
|
+
INFO = "INFO"
|
|
18
|
+
WARNING = "WARNING"
|
|
19
|
+
ERROR = "ERROR"
|
|
20
|
+
CRITICAL = "CRITICAL"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# Create the main Typer app
|
|
24
|
+
app = typer.Typer(
|
|
25
|
+
name="ghnova",
|
|
26
|
+
help="Main CLI for ghnova.",
|
|
27
|
+
rich_markup_mode="rich",
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def setup_logging(level: LoggingLevel = LoggingLevel.INFO) -> None:
|
|
32
|
+
"""Set up logging with Rich handler.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
level: Logging level.
|
|
36
|
+
"""
|
|
37
|
+
import logging
|
|
38
|
+
|
|
39
|
+
from rich.console import Console
|
|
40
|
+
from rich.logging import RichHandler
|
|
41
|
+
|
|
42
|
+
logger = logging.getLogger("ghnova")
|
|
43
|
+
|
|
44
|
+
logger.setLevel(level.value)
|
|
45
|
+
|
|
46
|
+
console = Console(stderr=True)
|
|
47
|
+
|
|
48
|
+
# Remove any existing handlers to ensure RichHandler is used
|
|
49
|
+
for h in logger.handlers[:]: # Use slice copy to avoid modification during iteration
|
|
50
|
+
logger.removeHandler(h)
|
|
51
|
+
# Add the RichHandler
|
|
52
|
+
|
|
53
|
+
handler = RichHandler(
|
|
54
|
+
console=console,
|
|
55
|
+
rich_tracebacks=True,
|
|
56
|
+
show_time=True,
|
|
57
|
+
show_level=True, # Keep level (e.g., DEBUG, INFO) for clarity
|
|
58
|
+
markup=True, # Enable Rich markup in messages for styling
|
|
59
|
+
level=level.value, # Ensure handler respects the level
|
|
60
|
+
omit_repeated_times=False,
|
|
61
|
+
log_time_format="%H:%M",
|
|
62
|
+
)
|
|
63
|
+
handler.setLevel(level.value)
|
|
64
|
+
logger.addHandler(handler)
|
|
65
|
+
|
|
66
|
+
# Prevent propagation to root logger to avoid duplicate output
|
|
67
|
+
logger.propagate = False
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@app.callback()
|
|
71
|
+
def main(
|
|
72
|
+
ctx: typer.Context,
|
|
73
|
+
config_path: Annotated[
|
|
74
|
+
str | None,
|
|
75
|
+
typer.Option(
|
|
76
|
+
"--config-path",
|
|
77
|
+
help="Path to the configuration file. If not provided, it uses the path specified by `GHNOVA_CONFIG_PATH`. If the environment variable is not defined, it uses the default location.",
|
|
78
|
+
),
|
|
79
|
+
] = None,
|
|
80
|
+
verbose: Annotated[
|
|
81
|
+
LoggingLevel,
|
|
82
|
+
typer.Option("--verbose", "-v", help="Set verbosity level."),
|
|
83
|
+
] = LoggingLevel.INFO,
|
|
84
|
+
) -> None:
|
|
85
|
+
"""Main entry point for the CLI application.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
ctx: Typer context.
|
|
89
|
+
config_path: Path to the configuration file.
|
|
90
|
+
verbose: Verbosity level for logging.
|
|
91
|
+
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
import os
|
|
95
|
+
|
|
96
|
+
config_path = config_path or os.getenv("GHNOVA_CONFIG_PATH")
|
|
97
|
+
|
|
98
|
+
ctx.obj = {"config_path": config_path}
|
|
99
|
+
|
|
100
|
+
setup_logging(verbose)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def register_commands() -> None:
|
|
104
|
+
"""Register CLI commands."""
|
|
105
|
+
|
|
106
|
+
from ghnova.cli.config.main import config_app # noqa: PLC0415
|
|
107
|
+
from ghnova.cli.issue.main import issue_app # noqa: PLC0415
|
|
108
|
+
from ghnova.cli.repository.main import repository_app # noqa: PLC0415
|
|
109
|
+
from ghnova.cli.user.main import user_app # noqa: PLC0415
|
|
110
|
+
|
|
111
|
+
app.add_typer(config_app)
|
|
112
|
+
app.add_typer(issue_app)
|
|
113
|
+
app.add_typer(repository_app)
|
|
114
|
+
app.add_typer(user_app)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
register_commands()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Command line interface for repository-related operations."""
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"""List command for repository 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
|
+
owner: Annotated[
|
|
14
|
+
str | None,
|
|
15
|
+
typer.Option(
|
|
16
|
+
"--owner",
|
|
17
|
+
help="The owner of the repositories.",
|
|
18
|
+
),
|
|
19
|
+
] = None,
|
|
20
|
+
organization: Annotated[
|
|
21
|
+
str | None,
|
|
22
|
+
typer.Option(
|
|
23
|
+
"--organization",
|
|
24
|
+
help="The organization name.",
|
|
25
|
+
),
|
|
26
|
+
] = None,
|
|
27
|
+
visibility: Annotated[
|
|
28
|
+
Literal["all", "public", "private"] | None,
|
|
29
|
+
typer.Option(
|
|
30
|
+
"--visibility",
|
|
31
|
+
help="Filter by visibility: all, public, or private.",
|
|
32
|
+
),
|
|
33
|
+
] = None,
|
|
34
|
+
affiliation: Annotated[
|
|
35
|
+
list[str] | None,
|
|
36
|
+
typer.Option(
|
|
37
|
+
"--affiliation",
|
|
38
|
+
help="Filter by affiliation: owner, collaborator, organization_member.",
|
|
39
|
+
),
|
|
40
|
+
] = None,
|
|
41
|
+
repository_type: Annotated[
|
|
42
|
+
Literal["all", "owner", "public", "private", "member"] | None,
|
|
43
|
+
typer.Option(
|
|
44
|
+
"--type",
|
|
45
|
+
help="Filter by repository type: all, owner, public, private, or member.",
|
|
46
|
+
),
|
|
47
|
+
] = None,
|
|
48
|
+
sort: Annotated[
|
|
49
|
+
Literal["created", "updated", "pushed", "full_name"] | None,
|
|
50
|
+
typer.Option(
|
|
51
|
+
"--sort",
|
|
52
|
+
help="Sort by: created, updated, pushed, or full_name.",
|
|
53
|
+
),
|
|
54
|
+
] = None,
|
|
55
|
+
direction: Annotated[
|
|
56
|
+
Literal["asc", "desc"] | None,
|
|
57
|
+
typer.Option(
|
|
58
|
+
"--direction",
|
|
59
|
+
help="Sort direction: asc or desc.",
|
|
60
|
+
),
|
|
61
|
+
] = None,
|
|
62
|
+
per_page: Annotated[
|
|
63
|
+
int,
|
|
64
|
+
typer.Option(
|
|
65
|
+
"--per-page",
|
|
66
|
+
help="Number of results per page.",
|
|
67
|
+
),
|
|
68
|
+
] = 30,
|
|
69
|
+
page: Annotated[
|
|
70
|
+
int,
|
|
71
|
+
typer.Option(
|
|
72
|
+
"--page",
|
|
73
|
+
help="Page number for pagination.",
|
|
74
|
+
),
|
|
75
|
+
] = 1,
|
|
76
|
+
since: Annotated[
|
|
77
|
+
datetime | None,
|
|
78
|
+
typer.Option(
|
|
79
|
+
"--since",
|
|
80
|
+
help="Only show repositories updated after this time (ISO 8601 format).",
|
|
81
|
+
),
|
|
82
|
+
] = None,
|
|
83
|
+
before: Annotated[
|
|
84
|
+
datetime | None,
|
|
85
|
+
typer.Option(
|
|
86
|
+
"--before",
|
|
87
|
+
help="Only show repositories updated before this time (ISO 8601 format).",
|
|
88
|
+
),
|
|
89
|
+
] = None,
|
|
90
|
+
etag: Annotated[
|
|
91
|
+
str | None,
|
|
92
|
+
typer.Option(
|
|
93
|
+
"--etag",
|
|
94
|
+
help="ETag header value for conditional requests.",
|
|
95
|
+
),
|
|
96
|
+
] = None,
|
|
97
|
+
last_modified: Annotated[
|
|
98
|
+
str | None,
|
|
99
|
+
typer.Option(
|
|
100
|
+
"--last-modified",
|
|
101
|
+
help="Last-Modified header value for conditional requests.",
|
|
102
|
+
),
|
|
103
|
+
] = None,
|
|
104
|
+
account_name: Annotated[
|
|
105
|
+
str | None,
|
|
106
|
+
typer.Option(
|
|
107
|
+
"--account-name",
|
|
108
|
+
help="Name of the account to use for authentication.",
|
|
109
|
+
),
|
|
110
|
+
] = None,
|
|
111
|
+
token: Annotated[
|
|
112
|
+
str | None,
|
|
113
|
+
typer.Option(
|
|
114
|
+
"--token",
|
|
115
|
+
help="Token for authentication. If not provided, the token from the specified account will be used.",
|
|
116
|
+
),
|
|
117
|
+
] = None,
|
|
118
|
+
base_url: Annotated[
|
|
119
|
+
str | None,
|
|
120
|
+
typer.Option(
|
|
121
|
+
"--base-url",
|
|
122
|
+
help="Base URL of the GitHub platform. If not provided, the base URL from the specified account will be used.",
|
|
123
|
+
),
|
|
124
|
+
] = None,
|
|
125
|
+
) -> None:
|
|
126
|
+
"""List repositories.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
ctx: Typer context.
|
|
130
|
+
owner: The owner of the repositories.
|
|
131
|
+
organization: The organization name.
|
|
132
|
+
visibility: Filter by visibility: all, public, or private.
|
|
133
|
+
affiliation: Filter by affiliation: owner, collaborator, or organization_member.
|
|
134
|
+
repository_type: Filter by repository type: all, owner, public, private, or member.
|
|
135
|
+
sort: Sort by: created, updated, pushed, or full_name.
|
|
136
|
+
direction: Sort direction: asc or desc.
|
|
137
|
+
per_page: Number of results per page.
|
|
138
|
+
page: Page number for pagination.
|
|
139
|
+
since: Only show repositories updated after this time.
|
|
140
|
+
before: Only show repositories updated before this time.
|
|
141
|
+
etag: ETag header value for conditional requests.
|
|
142
|
+
last_modified: Last-Modified header value for conditional requests.
|
|
143
|
+
account_name: Name of the account to use for authentication.
|
|
144
|
+
token: Token for authentication.
|
|
145
|
+
base_url: Base URL of the GitHub platform.
|
|
146
|
+
|
|
147
|
+
"""
|
|
148
|
+
import json # noqa: PLC0415
|
|
149
|
+
import logging # noqa: PLC0415
|
|
150
|
+
from typing import cast # noqa: PLC0415
|
|
151
|
+
|
|
152
|
+
from ghnova.cli.utils.auth import get_auth_params # noqa: PLC0415
|
|
153
|
+
from ghnova.client.github import GitHub # noqa: PLC0415
|
|
154
|
+
|
|
155
|
+
logger = logging.getLogger("ghnova")
|
|
156
|
+
|
|
157
|
+
token, base_url = get_auth_params(
|
|
158
|
+
config_path=ctx.obj["config_path"],
|
|
159
|
+
account_name=account_name,
|
|
160
|
+
token=token,
|
|
161
|
+
base_url=base_url,
|
|
162
|
+
)
|
|
163
|
+
affiliation_list = None
|
|
164
|
+
if affiliation:
|
|
165
|
+
if not all(a in {"owner", "collaborator", "organization_member"} for a in affiliation):
|
|
166
|
+
logger.error(
|
|
167
|
+
"Invalid affiliation value. Must be a comma-separated list of: owner, collaborator, organization_member."
|
|
168
|
+
)
|
|
169
|
+
raise typer.Exit(code=1)
|
|
170
|
+
affiliation_list = cast(list[Literal["owner", "collaborator", "organization_member"]], affiliation)
|
|
171
|
+
|
|
172
|
+
try:
|
|
173
|
+
with GitHub(token=token, base_url=base_url) as client:
|
|
174
|
+
repository_client = client.repository
|
|
175
|
+
data, status_code, etag_value, last_modified_value = repository_client.list_repositories(
|
|
176
|
+
owner=owner,
|
|
177
|
+
organization=organization,
|
|
178
|
+
visibility=visibility,
|
|
179
|
+
affiliation=affiliation_list,
|
|
180
|
+
repository_type=repository_type,
|
|
181
|
+
sort=sort,
|
|
182
|
+
direction=direction,
|
|
183
|
+
per_page=per_page,
|
|
184
|
+
page=page,
|
|
185
|
+
since=since,
|
|
186
|
+
before=before,
|
|
187
|
+
etag=etag,
|
|
188
|
+
last_modified=last_modified,
|
|
189
|
+
)
|
|
190
|
+
result = {
|
|
191
|
+
"data": data,
|
|
192
|
+
"metadata": {
|
|
193
|
+
"status_code": status_code,
|
|
194
|
+
"etag": etag_value,
|
|
195
|
+
"last_modified": last_modified_value,
|
|
196
|
+
},
|
|
197
|
+
}
|
|
198
|
+
print(json.dumps(result, indent=2, default=str))
|
|
199
|
+
except Exception as e:
|
|
200
|
+
logger.exception("Error listing repositories: %s", e)
|
|
201
|
+
raise typer.Exit(code=1) from e
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Repository CLI commands for ghnova."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
repository_app = typer.Typer(
|
|
8
|
+
name="repository",
|
|
9
|
+
help="Manage git repositories.",
|
|
10
|
+
rich_markup_mode="rich",
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def register_commands() -> None:
|
|
15
|
+
"""Register repository subcommands."""
|
|
16
|
+
from ghnova.cli.repository.list import list_command # noqa: PLC0415
|
|
17
|
+
|
|
18
|
+
repository_app.command(name="list", help="List repositories.")(list_command)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
register_commands()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Command line interface for user-related operations."""
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""Get contextual information command for user CLI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Annotated
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def contextual_information_command( # noqa: PLR0913
|
|
11
|
+
ctx: typer.Context,
|
|
12
|
+
account_name: Annotated[
|
|
13
|
+
str | None,
|
|
14
|
+
typer.Option(
|
|
15
|
+
"--account-name",
|
|
16
|
+
help="Name of the account to use for authentication.",
|
|
17
|
+
),
|
|
18
|
+
] = None,
|
|
19
|
+
token: Annotated[
|
|
20
|
+
str | None,
|
|
21
|
+
typer.Option(
|
|
22
|
+
"--token",
|
|
23
|
+
help="Token for authentication. If not provided, the token from the specified account will be used.",
|
|
24
|
+
),
|
|
25
|
+
] = None,
|
|
26
|
+
base_url: Annotated[
|
|
27
|
+
str | None,
|
|
28
|
+
typer.Option(
|
|
29
|
+
"--base-url",
|
|
30
|
+
help="Base URL of the GitHub platform. If not provided, the base URL from the specified account will be used.",
|
|
31
|
+
),
|
|
32
|
+
] = None,
|
|
33
|
+
username: Annotated[
|
|
34
|
+
str | None,
|
|
35
|
+
typer.Option(
|
|
36
|
+
"--username",
|
|
37
|
+
help="The username of the user to retrieve contextual information for.",
|
|
38
|
+
),
|
|
39
|
+
] = None,
|
|
40
|
+
subject_type: Annotated[
|
|
41
|
+
str | None,
|
|
42
|
+
typer.Option(
|
|
43
|
+
"--subject-type",
|
|
44
|
+
help="The type of subject for the hovercard (e.g., 'organization', 'repository', 'issue', 'pull_request').",
|
|
45
|
+
),
|
|
46
|
+
] = None,
|
|
47
|
+
subject_id: Annotated[
|
|
48
|
+
str | None,
|
|
49
|
+
typer.Option(
|
|
50
|
+
"--subject-id",
|
|
51
|
+
help="The ID of the subject for the hovercard.",
|
|
52
|
+
),
|
|
53
|
+
] = None,
|
|
54
|
+
) -> None:
|
|
55
|
+
"""Get contextual information about a user on GitHub.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
ctx: Typer context.
|
|
59
|
+
username: The username of the user to retrieve contextual information for.
|
|
60
|
+
account_name: Name of the account to use for authentication.
|
|
61
|
+
token: Token for authentication.
|
|
62
|
+
base_url: Base URL of the GitHub platform.
|
|
63
|
+
subject_type: The type of subject for the hovercard.
|
|
64
|
+
subject_id: The ID of the subject for the hovercard.
|
|
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
|
+
if username is None:
|
|
76
|
+
logger.error("Username must be provided to retrieve contextual information.")
|
|
77
|
+
raise typer.Exit(code=1)
|
|
78
|
+
|
|
79
|
+
token, base_url = get_auth_params(
|
|
80
|
+
config_path=ctx.obj["config_path"],
|
|
81
|
+
account_name=account_name,
|
|
82
|
+
token=token,
|
|
83
|
+
base_url=base_url,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
with GitHub(token=token, base_url=base_url) as client:
|
|
88
|
+
user_client = client.user
|
|
89
|
+
data, status_code, etag_value, last_modified_value = user_client.get_contextual_information(
|
|
90
|
+
username=username,
|
|
91
|
+
subject_type=subject_type,
|
|
92
|
+
subject_id=subject_id,
|
|
93
|
+
)
|
|
94
|
+
result = {
|
|
95
|
+
"data": data,
|
|
96
|
+
"metadata": {
|
|
97
|
+
"status_code": status_code,
|
|
98
|
+
"etag": etag_value,
|
|
99
|
+
"last_modified": last_modified_value,
|
|
100
|
+
},
|
|
101
|
+
}
|
|
102
|
+
print(json.dumps(result, indent=4))
|
|
103
|
+
except Exception as e:
|
|
104
|
+
logger.error("Error retrieving contextual information: %s", e)
|
|
105
|
+
raise typer.Exit(code=1) from e
|
ghnova/cli/user/get.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""Get command for user CLI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Annotated
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_command( # noqa: PLR0913
|
|
11
|
+
ctx: typer.Context,
|
|
12
|
+
account_name: Annotated[
|
|
13
|
+
str | None,
|
|
14
|
+
typer.Option(
|
|
15
|
+
"--account-name",
|
|
16
|
+
help="Name of the account to use for authentication.",
|
|
17
|
+
),
|
|
18
|
+
] = None,
|
|
19
|
+
token: Annotated[
|
|
20
|
+
str | None,
|
|
21
|
+
typer.Option(
|
|
22
|
+
"--token",
|
|
23
|
+
help="Token for authentication. If not provided, the token from the specified account will be used.",
|
|
24
|
+
),
|
|
25
|
+
] = None,
|
|
26
|
+
base_url: Annotated[
|
|
27
|
+
str | None,
|
|
28
|
+
typer.Option(
|
|
29
|
+
"--base-url",
|
|
30
|
+
help="Base URL of the GitHub platform. If not provided, the base URL from the specified account will be used.",
|
|
31
|
+
),
|
|
32
|
+
] = None,
|
|
33
|
+
username: Annotated[
|
|
34
|
+
str | None,
|
|
35
|
+
typer.Option(
|
|
36
|
+
"--username",
|
|
37
|
+
help="Username of the user to retrieve. If not provided, the authenticated user's information will be retrieved.",
|
|
38
|
+
),
|
|
39
|
+
] = None,
|
|
40
|
+
account_id: Annotated[
|
|
41
|
+
int | None,
|
|
42
|
+
typer.Option(
|
|
43
|
+
"--account-id",
|
|
44
|
+
help="Account ID of the user to retrieve.",
|
|
45
|
+
),
|
|
46
|
+
] = None,
|
|
47
|
+
etag: Annotated[
|
|
48
|
+
str | None,
|
|
49
|
+
typer.Option("--etag", help="ETag from a previous request for caching purposes."),
|
|
50
|
+
] = None,
|
|
51
|
+
last_modified: Annotated[
|
|
52
|
+
str | None,
|
|
53
|
+
typer.Option("--last-modified", help="Last-Modified header from a previous request for caching purposes."),
|
|
54
|
+
] = None,
|
|
55
|
+
):
|
|
56
|
+
"""Retrieve user information from GitHub.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
ctx: Typer context.
|
|
60
|
+
account_name: Name of the account to use for authentication.
|
|
61
|
+
token: Token for authentication.
|
|
62
|
+
base_url: Base URL of the GitHub platform.
|
|
63
|
+
username: Username of the user to retrieve.
|
|
64
|
+
account_id: Account ID of the user to retrieve.
|
|
65
|
+
etag: ETag from a previous request for caching purposes.
|
|
66
|
+
last_modified: Last-Modified header from a previous request for caching purposes.
|
|
67
|
+
|
|
68
|
+
"""
|
|
69
|
+
import json # noqa: PLC0415
|
|
70
|
+
import logging # noqa: PLC0415
|
|
71
|
+
|
|
72
|
+
from ghnova.cli.utils.auth import get_auth_params # noqa: PLC0415
|
|
73
|
+
from ghnova.client.github import GitHub # noqa: PLC0415
|
|
74
|
+
|
|
75
|
+
logger = logging.getLogger("ghnova")
|
|
76
|
+
|
|
77
|
+
token, base_url = get_auth_params(
|
|
78
|
+
config_path=ctx.obj["config_path"], account_name=account_name, token=token, base_url=base_url
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
with GitHub(token=token, base_url=base_url) as client:
|
|
83
|
+
user_client = client.user
|
|
84
|
+
data, status_code, etag_value, last_modified_value = user_client.get_user(
|
|
85
|
+
username=username, account_id=account_id, etag=etag, last_modified=last_modified
|
|
86
|
+
)
|
|
87
|
+
result = {
|
|
88
|
+
"data": data,
|
|
89
|
+
"metadata": {
|
|
90
|
+
"status_code": status_code,
|
|
91
|
+
"etag": etag_value,
|
|
92
|
+
"last_modified": last_modified_value,
|
|
93
|
+
},
|
|
94
|
+
}
|
|
95
|
+
print(json.dumps(result, indent=2, default=str))
|
|
96
|
+
except Exception as e:
|
|
97
|
+
logger.error("Error retrieving user information: %s", e)
|
|
98
|
+
raise typer.Exit(code=1) from e
|
ghnova/cli/user/list.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""CLI for listing users."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Annotated
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def list_command( # noqa: D103, PLR0913
|
|
11
|
+
ctx: typer.Context,
|
|
12
|
+
account_name: Annotated[
|
|
13
|
+
str | None,
|
|
14
|
+
typer.Option(
|
|
15
|
+
"--account-name",
|
|
16
|
+
help="Name of the account to use for authentication.",
|
|
17
|
+
),
|
|
18
|
+
] = None,
|
|
19
|
+
token: Annotated[
|
|
20
|
+
str | None,
|
|
21
|
+
typer.Option(
|
|
22
|
+
"--token",
|
|
23
|
+
help="Token for authentication. If not provided, the token from the specified account will be used.",
|
|
24
|
+
),
|
|
25
|
+
] = None,
|
|
26
|
+
base_url: Annotated[
|
|
27
|
+
str | None,
|
|
28
|
+
typer.Option(
|
|
29
|
+
"--base-url",
|
|
30
|
+
help="Base URL of the GitHub platform. If not provided, the base URL from the specified account will be used.",
|
|
31
|
+
),
|
|
32
|
+
] = None,
|
|
33
|
+
since: Annotated[
|
|
34
|
+
int | None,
|
|
35
|
+
typer.Option(
|
|
36
|
+
"--since",
|
|
37
|
+
help="A user ID. Only return users with an ID greater than this ID.",
|
|
38
|
+
),
|
|
39
|
+
] = None,
|
|
40
|
+
per_page: Annotated[int | None, typer.Option("--per-page", help="Number of results per page (max 100).")] = None,
|
|
41
|
+
etag: Annotated[
|
|
42
|
+
str | None, typer.Option("--etag", help="ETag from a previous request for caching purposes.")
|
|
43
|
+
] = None,
|
|
44
|
+
last_modified: Annotated[
|
|
45
|
+
str | None,
|
|
46
|
+
typer.Option("--last-modified", help="Last-Modified header from a previous request for caching purposes."),
|
|
47
|
+
] = None,
|
|
48
|
+
):
|
|
49
|
+
import json # noqa: PLC0415
|
|
50
|
+
import logging # noqa: PLC0415
|
|
51
|
+
|
|
52
|
+
from ghnova.cli.utils.auth import get_auth_params # noqa: PLC0415
|
|
53
|
+
from ghnova.client.github import GitHub # noqa: PLC0415
|
|
54
|
+
|
|
55
|
+
logger = logging.getLogger("ghnova")
|
|
56
|
+
|
|
57
|
+
token, base_url = get_auth_params(
|
|
58
|
+
config_path=ctx.obj["config_path"], account_name=account_name, token=token, base_url=base_url
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
try:
|
|
62
|
+
with GitHub(token=token, base_url=base_url) as client:
|
|
63
|
+
user_client = client.user
|
|
64
|
+
data, status_code, etag_value, last_modified_value = user_client.list_users(
|
|
65
|
+
since=since, per_page=per_page, etag=etag, last_modified=last_modified
|
|
66
|
+
)
|
|
67
|
+
result = {
|
|
68
|
+
"data": data,
|
|
69
|
+
"metadata": {
|
|
70
|
+
"status_code": status_code,
|
|
71
|
+
"etag": etag_value,
|
|
72
|
+
"last_modified": last_modified_value,
|
|
73
|
+
},
|
|
74
|
+
}
|
|
75
|
+
typer.echo(json.dumps(result, indent=2, default=str))
|
|
76
|
+
except Exception as e:
|
|
77
|
+
logger.error("Error listing users: %s", e)
|
|
78
|
+
raise typer.Exit(code=1) from e
|
ghnova/cli/user/main.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""User CLI commands for ghnova."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
user_app = typer.Typer(
|
|
8
|
+
name="user",
|
|
9
|
+
help="Manage git users.",
|
|
10
|
+
rich_markup_mode="rich",
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def register_commands() -> None:
|
|
15
|
+
"""Register user subcommands."""
|
|
16
|
+
from ghnova.cli.user.ctx_info import contextual_information_command # noqa: PLC0415
|
|
17
|
+
from ghnova.cli.user.get import get_command # noqa: PLC0415
|
|
18
|
+
from ghnova.cli.user.list import list_command # noqa: PLC0415
|
|
19
|
+
from ghnova.cli.user.update import update_command # noqa: PLC0415
|
|
20
|
+
|
|
21
|
+
user_app.command(name="get")(get_command)
|
|
22
|
+
user_app.command(name="list")(list_command)
|
|
23
|
+
user_app.command(name="update")(update_command)
|
|
24
|
+
user_app.command(name="ctx-info")(contextual_information_command)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
register_commands()
|