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/config/manager.py
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"""Configuration manager for ghnova."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import platformdirs
|
|
9
|
+
import yaml
|
|
10
|
+
|
|
11
|
+
from ghnova.config.model import AccountConfig, Config
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger("ghnova")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ConfigManager:
|
|
17
|
+
"""Configuration manager for ghnova."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, filename: Path | str | None = None) -> None:
|
|
20
|
+
"""Initialize ConfigManager.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
filename: Name of the configuration file.
|
|
24
|
+
|
|
25
|
+
"""
|
|
26
|
+
filename = filename or Path(platformdirs.user_config_dir(appname="ghnova")) / "config.yaml"
|
|
27
|
+
filename = Path(filename)
|
|
28
|
+
filename.parent.mkdir(parents=True, exist_ok=True)
|
|
29
|
+
self.config_path = filename
|
|
30
|
+
self._config: Config | None = None
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def config(self) -> Config:
|
|
34
|
+
"""Get the current configuration.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
Config: Current configuration.
|
|
38
|
+
|
|
39
|
+
"""
|
|
40
|
+
if self._config is None:
|
|
41
|
+
self._config = self._load_config()
|
|
42
|
+
return self._config
|
|
43
|
+
|
|
44
|
+
@config.setter
|
|
45
|
+
def config(self, value: Config) -> None:
|
|
46
|
+
"""Set the current configuration.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
value: New configuration to set.
|
|
50
|
+
|
|
51
|
+
"""
|
|
52
|
+
self._config = value
|
|
53
|
+
|
|
54
|
+
def get_config(self, name: str | None) -> AccountConfig:
|
|
55
|
+
"""Get the configuration for a specific account.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
name: Name of the account. If None, the default account is used.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
AccountConfig: Configuration of the specified account.
|
|
62
|
+
|
|
63
|
+
"""
|
|
64
|
+
if self._config is None:
|
|
65
|
+
self.load_config()
|
|
66
|
+
|
|
67
|
+
name = self.config.default_account if name is None else name
|
|
68
|
+
|
|
69
|
+
if self._config is None:
|
|
70
|
+
self.load_config()
|
|
71
|
+
|
|
72
|
+
if name not in self.config.accounts:
|
|
73
|
+
raise ValueError(f"Account '{name}' does not exist in the configuration.")
|
|
74
|
+
|
|
75
|
+
return self.config.accounts[name]
|
|
76
|
+
|
|
77
|
+
def add_account(
|
|
78
|
+
self, name: str, token: str, base_url: str = "https://github.com", is_default: bool = False
|
|
79
|
+
) -> None:
|
|
80
|
+
"""Add a new account to the configuration.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
name: Name of the account.
|
|
84
|
+
token: Authentication token for the account.
|
|
85
|
+
base_url: Base URL of the GitHub platform.
|
|
86
|
+
is_default: Set as default account.
|
|
87
|
+
|
|
88
|
+
"""
|
|
89
|
+
if self._config is None:
|
|
90
|
+
self.load_config()
|
|
91
|
+
|
|
92
|
+
if name in self.config.accounts:
|
|
93
|
+
raise ValueError(f"Account '{name}' already exists in the configuration.")
|
|
94
|
+
|
|
95
|
+
self.config.accounts[name] = AccountConfig(name=name, token=token, base_url=base_url)
|
|
96
|
+
|
|
97
|
+
if is_default or len(self.config.accounts) == 1:
|
|
98
|
+
self.config.default_account = name
|
|
99
|
+
|
|
100
|
+
def update_account(
|
|
101
|
+
self,
|
|
102
|
+
name: str,
|
|
103
|
+
token: str | None = None,
|
|
104
|
+
base_url: str | None = None,
|
|
105
|
+
is_default: bool | None = None,
|
|
106
|
+
) -> None:
|
|
107
|
+
"""Update an existing account in the configuration.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
name: Name of the account to update.
|
|
111
|
+
token: New authentication token for the account (optional).
|
|
112
|
+
base_url: New base URL of the account (optional).
|
|
113
|
+
is_default: Set as default account (optional).
|
|
114
|
+
|
|
115
|
+
"""
|
|
116
|
+
if self._config is None:
|
|
117
|
+
self.load_config()
|
|
118
|
+
|
|
119
|
+
if name not in self.config.accounts:
|
|
120
|
+
raise ValueError(f"Account '{name}' does not exist in the configuration.")
|
|
121
|
+
|
|
122
|
+
account = self.config.accounts[name]
|
|
123
|
+
|
|
124
|
+
if token is not None:
|
|
125
|
+
account.token = token
|
|
126
|
+
if base_url is not None:
|
|
127
|
+
account.base_url = base_url
|
|
128
|
+
|
|
129
|
+
if is_default is not None:
|
|
130
|
+
if is_default:
|
|
131
|
+
self.config.default_account = name
|
|
132
|
+
elif self.config.default_account == name:
|
|
133
|
+
self.config.default_account = None
|
|
134
|
+
else:
|
|
135
|
+
logger.warning("Account '%s' is not the default account. No changes made to default account.", name)
|
|
136
|
+
|
|
137
|
+
def delete_account(self, name: str) -> None:
|
|
138
|
+
"""Delete an account from the configuration.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
name: Name of the account to delete.
|
|
142
|
+
|
|
143
|
+
"""
|
|
144
|
+
if self._config is None:
|
|
145
|
+
self.load_config()
|
|
146
|
+
|
|
147
|
+
if name not in self.config.accounts:
|
|
148
|
+
raise ValueError(f"Account '{name}' does not exist in the configuration.")
|
|
149
|
+
|
|
150
|
+
del self.config.accounts[name]
|
|
151
|
+
|
|
152
|
+
if self.config.default_account == name:
|
|
153
|
+
self.config.default_account = None
|
|
154
|
+
|
|
155
|
+
def _load_config(self, filename: Path | str | None = None) -> Config:
|
|
156
|
+
"""Load configuration from the YAML file.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
filename: Optional path to the configuration file.
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
Config: Loaded configuration.
|
|
163
|
+
|
|
164
|
+
"""
|
|
165
|
+
filename = filename or self.config_path
|
|
166
|
+
filename = Path(filename)
|
|
167
|
+
if not filename.exists():
|
|
168
|
+
return Config()
|
|
169
|
+
|
|
170
|
+
with filename.open("r", encoding="utf-8") as file:
|
|
171
|
+
raw_config = yaml.safe_load(file) or {}
|
|
172
|
+
|
|
173
|
+
try:
|
|
174
|
+
return Config(**raw_config)
|
|
175
|
+
except ValueError as e:
|
|
176
|
+
raise ValueError(f"Invalid configuration format: {e}") from e
|
|
177
|
+
|
|
178
|
+
def load_config(self, filename: Path | str | None = None) -> None:
|
|
179
|
+
"""Load configuration from the YAML file.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
filename: Optional path to the configuration file.
|
|
183
|
+
|
|
184
|
+
"""
|
|
185
|
+
self.config = self._load_config(filename)
|
|
186
|
+
|
|
187
|
+
def save_config(self, filename: Path | str | None = None) -> None:
|
|
188
|
+
"""Save configuration to the YAML file.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
filename: Optional path to the configuration file.
|
|
192
|
+
|
|
193
|
+
"""
|
|
194
|
+
filename = filename or self.config_path
|
|
195
|
+
filename = Path(filename)
|
|
196
|
+
with filename.open("w", encoding="utf-8") as file:
|
|
197
|
+
yaml.safe_dump(self.config.model_dump(), file)
|
|
198
|
+
|
|
199
|
+
def has_default_account(self) -> bool:
|
|
200
|
+
"""Check if a default account is set.
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
bool: True if a default account is set, False otherwise.
|
|
204
|
+
|
|
205
|
+
"""
|
|
206
|
+
if self._config is None:
|
|
207
|
+
self.load_config()
|
|
208
|
+
|
|
209
|
+
return self.config.default_account is not None
|
ghnova/config/model.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Configuration model for GitHub accounts."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field, field_validator
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class AccountConfig(BaseModel):
|
|
9
|
+
"""Configuration for a GitHub account."""
|
|
10
|
+
|
|
11
|
+
name: str = Field(..., description="Label of the GitHub account (e.g., 'my_account').")
|
|
12
|
+
"""Name of the platform (e.g., 'github', 'gitlab')."""
|
|
13
|
+
token: str = Field(..., description="Authentication token for GitHub.")
|
|
14
|
+
"""Authentication token for the account."""
|
|
15
|
+
base_url: str = Field(default="https://github.com", description="Base URL for GitHub.")
|
|
16
|
+
"""Base URL for the GitHub platform."""
|
|
17
|
+
|
|
18
|
+
def __repr__(self) -> str:
|
|
19
|
+
"""Return a string representation of the AccountConfig.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
A string representation of the AccountConfig.
|
|
23
|
+
|
|
24
|
+
"""
|
|
25
|
+
return f"AccountConfig(name={self.name}, base_url={self.base_url})"
|
|
26
|
+
|
|
27
|
+
@field_validator("base_url")
|
|
28
|
+
@classmethod
|
|
29
|
+
def validate_base_url(cls, v: str) -> str:
|
|
30
|
+
"""Validate and normalize the base_url field.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
v: The base URL to validate.
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
The normalized base URL.
|
|
37
|
+
|
|
38
|
+
"""
|
|
39
|
+
if not v.startswith(("http://", "https://")):
|
|
40
|
+
raise ValueError("base_url must start with http:// or https://")
|
|
41
|
+
|
|
42
|
+
return v.rstrip("/")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class Config(BaseModel):
|
|
46
|
+
"""Main configuration model for ghnova."""
|
|
47
|
+
|
|
48
|
+
accounts: dict[str, AccountConfig] = Field(
|
|
49
|
+
default_factory=dict,
|
|
50
|
+
description="Dictionary of account configurations.",
|
|
51
|
+
)
|
|
52
|
+
"""Dictionary of account configurations."""
|
|
53
|
+
|
|
54
|
+
default_account: str | None = Field(
|
|
55
|
+
default=None,
|
|
56
|
+
description="Name of the default account to use.",
|
|
57
|
+
)
|
|
58
|
+
"""Name of the default account to use."""
|