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,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."""
@@ -0,0 +1,8 @@
1
+ """GitHub Issue module."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from ghnova.issue.async_issue import AsyncIssue
6
+ from ghnova.issue.issue import Issue
7
+
8
+ __all__ = ["AsyncIssue", "Issue"]