gitcode-api 1.1.2__py3-none-any.whl → 1.1.3__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.
- gitcode_api/__main__.py +4 -0
- gitcode_api/cli.py +255 -0
- gitcode_api/version.txt +1 -1
- {gitcode_api-1.1.2.dist-info → gitcode_api-1.1.3.dist-info}/METADATA +15 -2
- {gitcode_api-1.1.2.dist-info → gitcode_api-1.1.3.dist-info}/RECORD +9 -6
- gitcode_api-1.1.3.dist-info/entry_points.txt +2 -0
- {gitcode_api-1.1.2.dist-info → gitcode_api-1.1.3.dist-info}/WHEEL +0 -0
- {gitcode_api-1.1.2.dist-info → gitcode_api-1.1.3.dist-info}/licenses/LICENSE +0 -0
- {gitcode_api-1.1.2.dist-info → gitcode_api-1.1.3.dist-info}/top_level.txt +0 -0
gitcode_api/__main__.py
ADDED
gitcode_api/cli.py
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
"""Command-line interface for the GitCode SDK."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import inspect
|
|
5
|
+
import json
|
|
6
|
+
import sys
|
|
7
|
+
from collections.abc import Mapping, Sequence
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any, List, Optional, Union, get_args, get_origin
|
|
10
|
+
|
|
11
|
+
from . import GitCode, __version__
|
|
12
|
+
from ._base_client import DEFAULT_BASE_URL, DEFAULT_TOKEN_ENV
|
|
13
|
+
from ._exceptions import GitCodeError
|
|
14
|
+
from .resources._shared import SyncResource
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _unwrap_optional(annotation: Any) -> Any:
|
|
18
|
+
origin = get_origin(annotation)
|
|
19
|
+
if origin is Union:
|
|
20
|
+
args = [arg for arg in get_args(annotation) if arg is not type(None)]
|
|
21
|
+
if len(args) == 1:
|
|
22
|
+
return args[0]
|
|
23
|
+
return annotation
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _is_list_annotation(annotation: Any) -> bool:
|
|
27
|
+
annotation = _unwrap_optional(annotation)
|
|
28
|
+
return get_origin(annotation) in (list, List)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _list_item_type(annotation: Any) -> Any:
|
|
32
|
+
annotation = _unwrap_optional(annotation)
|
|
33
|
+
args = get_args(annotation)
|
|
34
|
+
if not args:
|
|
35
|
+
return str
|
|
36
|
+
item_type = _unwrap_optional(args[0])
|
|
37
|
+
if item_type in (int, float):
|
|
38
|
+
return item_type
|
|
39
|
+
return str
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _argument_kwargs(parameter: inspect.Parameter) -> dict[str, Any]:
|
|
43
|
+
annotation = _unwrap_optional(parameter.annotation)
|
|
44
|
+
if annotation is bool:
|
|
45
|
+
return {"action": argparse.BooleanOptionalAction, "default": parameter.default}
|
|
46
|
+
if _is_list_annotation(parameter.annotation):
|
|
47
|
+
return {"nargs": "+", "type": _list_item_type(parameter.annotation), "default": None}
|
|
48
|
+
if annotation in (int, float):
|
|
49
|
+
return {"type": annotation}
|
|
50
|
+
return {"type": str}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _first_doc_line(obj: Any) -> str:
|
|
54
|
+
doc = inspect.getdoc(obj) or ""
|
|
55
|
+
return doc.splitlines()[0] if doc else ""
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _resource_types() -> dict[str, type[SyncResource]]:
|
|
59
|
+
resources: dict[str, type[SyncResource]] = {}
|
|
60
|
+
for name, annotation in GitCode.__annotations__.items():
|
|
61
|
+
if inspect.isclass(annotation) and issubclass(annotation, SyncResource):
|
|
62
|
+
resources[name] = annotation
|
|
63
|
+
return resources
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _iter_resource_methods(resource_type: type[SyncResource]) -> list[tuple[str, Any]]:
|
|
67
|
+
methods: list[tuple[str, Any]] = []
|
|
68
|
+
for name, value in resource_type.__dict__.items():
|
|
69
|
+
if name.startswith("_") or not inspect.isfunction(value):
|
|
70
|
+
continue
|
|
71
|
+
methods.append((name, value))
|
|
72
|
+
return methods
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _kebab_case(value: str) -> str:
|
|
76
|
+
return value.replace("_", "-")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _load_json_value(raw: str) -> Any:
|
|
80
|
+
if raw.startswith("@"):
|
|
81
|
+
return json.loads(Path(raw[1:]).read_text(encoding="utf-8"))
|
|
82
|
+
return json.loads(raw)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _parse_scalar(raw: str) -> Any:
|
|
86
|
+
try:
|
|
87
|
+
return _load_json_value(raw)
|
|
88
|
+
except (OSError, ValueError, json.JSONDecodeError):
|
|
89
|
+
return raw
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _parse_key_value(raw: str) -> tuple[str, Any]:
|
|
93
|
+
if "=" not in raw:
|
|
94
|
+
raise ValueError(f"Expected KEY=VALUE, got: {raw}")
|
|
95
|
+
key, value = raw.split("=", maxsplit=1)
|
|
96
|
+
if not key:
|
|
97
|
+
raise ValueError(f"Expected KEY=VALUE, got: {raw}")
|
|
98
|
+
return key, _parse_scalar(value)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _to_data(value: Any) -> Any:
|
|
102
|
+
if hasattr(value, "to_dict") and callable(value.to_dict):
|
|
103
|
+
return _to_data(value.to_dict())
|
|
104
|
+
if isinstance(value, Mapping):
|
|
105
|
+
return {key: _to_data(item) for key, item in value.items()}
|
|
106
|
+
if isinstance(value, list):
|
|
107
|
+
return [_to_data(item) for item in value]
|
|
108
|
+
return value
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _write_output(value: Any, *, output_file: Optional[str], compact: bool) -> None:
|
|
112
|
+
if value is None:
|
|
113
|
+
return
|
|
114
|
+
if isinstance(value, bytes):
|
|
115
|
+
if output_file:
|
|
116
|
+
Path(output_file).write_bytes(value)
|
|
117
|
+
else:
|
|
118
|
+
sys.stdout.buffer.write(value)
|
|
119
|
+
return
|
|
120
|
+
|
|
121
|
+
payload = _to_data(value)
|
|
122
|
+
if isinstance(payload, str):
|
|
123
|
+
text = payload
|
|
124
|
+
else:
|
|
125
|
+
text = json.dumps(payload, indent=None if compact else 2, ensure_ascii=True, sort_keys=True)
|
|
126
|
+
|
|
127
|
+
if output_file:
|
|
128
|
+
Path(output_file).write_text(text + ("\n" if not text.endswith("\n") else ""), encoding="utf-8")
|
|
129
|
+
else:
|
|
130
|
+
print(text)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _global_parent_parser() -> argparse.ArgumentParser:
|
|
134
|
+
parser = argparse.ArgumentParser(add_help=False)
|
|
135
|
+
parser.add_argument("--api-key", help=f"GitCode access token. Defaults to {DEFAULT_TOKEN_ENV}.")
|
|
136
|
+
parser.add_argument("--owner", help="Default repository owner.")
|
|
137
|
+
parser.add_argument("--repo", help="Default repository name.")
|
|
138
|
+
parser.add_argument("--base-url", default=DEFAULT_BASE_URL, help="Base URL for the GitCode REST API.")
|
|
139
|
+
parser.add_argument("--timeout", type=float, default=None, help="Request timeout in seconds.")
|
|
140
|
+
parser.add_argument("--output-file", help="Write the response to a file instead of stdout.")
|
|
141
|
+
parser.add_argument("--compact", action="store_true", help="Print JSON without indentation.")
|
|
142
|
+
return parser
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
146
|
+
common = _global_parent_parser()
|
|
147
|
+
parser = argparse.ArgumentParser(
|
|
148
|
+
prog="gitcode-api",
|
|
149
|
+
description="Invoke any synchronous gitcode-api resource method from the command line.",
|
|
150
|
+
epilog='Use `--set key=value` and `--set-json \'{"key": "value"}\'` for methods with `**params` or `**payload`.',
|
|
151
|
+
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
152
|
+
parents=[common],
|
|
153
|
+
)
|
|
154
|
+
parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
|
|
155
|
+
|
|
156
|
+
resource_parsers = parser.add_subparsers(dest="resource", required=True)
|
|
157
|
+
for resource_name, resource_type in _resource_types().items():
|
|
158
|
+
resource_parser = resource_parsers.add_parser(
|
|
159
|
+
_kebab_case(resource_name),
|
|
160
|
+
help=_first_doc_line(resource_type),
|
|
161
|
+
)
|
|
162
|
+
method_parsers = resource_parser.add_subparsers(dest="method", required=True)
|
|
163
|
+
|
|
164
|
+
for method_name, method in _iter_resource_methods(resource_type):
|
|
165
|
+
method_parser = method_parsers.add_parser(
|
|
166
|
+
_kebab_case(method_name),
|
|
167
|
+
help=_first_doc_line(method),
|
|
168
|
+
description=inspect.getdoc(method),
|
|
169
|
+
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
|
170
|
+
parents=[common],
|
|
171
|
+
)
|
|
172
|
+
signature = inspect.signature(method)
|
|
173
|
+
for parameter in signature.parameters.values():
|
|
174
|
+
if parameter.name == "self":
|
|
175
|
+
continue
|
|
176
|
+
if parameter.kind == inspect.Parameter.VAR_KEYWORD:
|
|
177
|
+
method_parser.add_argument(
|
|
178
|
+
"--set",
|
|
179
|
+
dest="extra_items",
|
|
180
|
+
action="append",
|
|
181
|
+
default=None,
|
|
182
|
+
metavar="KEY=VALUE",
|
|
183
|
+
help="Additional keyword arguments for `**params` or `**payload`.",
|
|
184
|
+
)
|
|
185
|
+
method_parser.add_argument(
|
|
186
|
+
"--set-json",
|
|
187
|
+
dest="extra_json",
|
|
188
|
+
default=None,
|
|
189
|
+
metavar="JSON_OR_@FILE",
|
|
190
|
+
help="JSON object merged into extra keyword arguments.",
|
|
191
|
+
)
|
|
192
|
+
continue
|
|
193
|
+
|
|
194
|
+
flag = f"--{parameter.name.replace('_', '-')}"
|
|
195
|
+
if flag in method_parser._option_string_actions:
|
|
196
|
+
continue
|
|
197
|
+
kwargs = _argument_kwargs(parameter)
|
|
198
|
+
kwargs["dest"] = parameter.name
|
|
199
|
+
kwargs["required"] = parameter.default is inspect.Signature.empty
|
|
200
|
+
method_parser.add_argument(flag, **kwargs)
|
|
201
|
+
|
|
202
|
+
method_parser.set_defaults(resource_name=resource_name, method_name=method_name)
|
|
203
|
+
|
|
204
|
+
return parser
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _collect_kwargs(args: argparse.Namespace, method: Any) -> dict[str, Any]:
|
|
208
|
+
signature = inspect.signature(method)
|
|
209
|
+
kwargs: dict[str, Any] = {}
|
|
210
|
+
for parameter in signature.parameters.values():
|
|
211
|
+
if parameter.name == "self":
|
|
212
|
+
continue
|
|
213
|
+
if parameter.kind == inspect.Parameter.VAR_KEYWORD:
|
|
214
|
+
extra_kwargs: dict[str, Any] = {}
|
|
215
|
+
if getattr(args, "extra_json", None):
|
|
216
|
+
raw_extra = _load_json_value(args.extra_json)
|
|
217
|
+
if not isinstance(raw_extra, dict):
|
|
218
|
+
raise ValueError("--set-json must decode to a JSON object.")
|
|
219
|
+
extra_kwargs.update(raw_extra)
|
|
220
|
+
for item in getattr(args, "extra_items", []) or []:
|
|
221
|
+
key, value = _parse_key_value(item)
|
|
222
|
+
extra_kwargs[key] = value
|
|
223
|
+
kwargs.update(extra_kwargs)
|
|
224
|
+
continue
|
|
225
|
+
|
|
226
|
+
value = getattr(args, parameter.name)
|
|
227
|
+
if value is None:
|
|
228
|
+
if parameter.default is inspect.Signature.empty:
|
|
229
|
+
raise ValueError(f"--{parameter.name.replace('_', '-')} is required.")
|
|
230
|
+
continue
|
|
231
|
+
kwargs[parameter.name] = value
|
|
232
|
+
return kwargs
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def main(argv: Optional[Sequence[str]] = None) -> int:
|
|
236
|
+
parser = build_parser()
|
|
237
|
+
args = parser.parse_args(argv)
|
|
238
|
+
|
|
239
|
+
try:
|
|
240
|
+
with GitCode(
|
|
241
|
+
api_key=args.api_key,
|
|
242
|
+
owner=args.owner,
|
|
243
|
+
repo=args.repo,
|
|
244
|
+
base_url=args.base_url,
|
|
245
|
+
timeout=args.timeout,
|
|
246
|
+
) as client:
|
|
247
|
+
resource = getattr(client, args.resource_name)
|
|
248
|
+
method = getattr(resource, args.method_name)
|
|
249
|
+
result = method(**_collect_kwargs(args, method))
|
|
250
|
+
except (GitCodeError, OSError, TypeError, ValueError) as exc: # pragma: no cover - integration style
|
|
251
|
+
print(f"error: {exc}", file=sys.stderr)
|
|
252
|
+
return 1
|
|
253
|
+
|
|
254
|
+
_write_output(result, output_file=args.output_file, compact=args.compact)
|
|
255
|
+
return 0
|
gitcode_api/version.txt
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.1.
|
|
1
|
+
1.1.3
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gitcode-api
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.3
|
|
4
4
|
Summary: Easy to use Python SDK for the GitCode REST API, community-maintained.
|
|
5
5
|
Author-email: Hugo Huang <hugo@hugohuang.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -46,7 +46,7 @@ Dynamic: license-file
|
|
|
46
46
|
Install from PyPI:
|
|
47
47
|
|
|
48
48
|
```bash
|
|
49
|
-
pip install gitcode-api
|
|
49
|
+
pip install -U gitcode-api
|
|
50
50
|
```
|
|
51
51
|
|
|
52
52
|
## Authentication
|
|
@@ -71,6 +71,19 @@ client = GitCode(
|
|
|
71
71
|
)
|
|
72
72
|
```
|
|
73
73
|
|
|
74
|
+
## CLI
|
|
75
|
+
|
|
76
|
+
After installation, you can invoke the SDK directly from the command line:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
gitcode-api repos get --api-key "$GITCODE_ACCESS_TOKEN" --owner SushiNinja --repo GitCode-API
|
|
80
|
+
python -m gitcode_api pulls list --api-key "$GITCODE_ACCESS_TOKEN" --owner SushiNinja --repo GitCode-API --state open
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Commands mirror the synchronous resource methods on `GitCode`, using the pattern
|
|
84
|
+
`gitcode-api <resource> <method> ...`. For methods that accept extra `**params`
|
|
85
|
+
or `**payload`, pass repeated `--set key=value` flags or `--set-json '{"key": "value"}'`.
|
|
86
|
+
|
|
74
87
|
## Quick Start
|
|
75
88
|
|
|
76
89
|
### Sync client
|
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
gitcode_api/__init__.py,sha256=NvLp28D9brVbmv2ROSZ8mxKDuIamE9xeutBKGl9pra8,497
|
|
2
|
+
gitcode_api/__main__.py,sha256=MHKZ_ae3fSLGTLUUMOx15fWdeOnJSHhq-zslRP5F5Lc,79
|
|
2
3
|
gitcode_api/_base_client.py,sha256=1COCZXzSiCe_LNawsOsTNvh0WMh1JW4A5yYgX56HwjY,12894
|
|
3
4
|
gitcode_api/_client.py,sha256=AOpp1P7vdSlMcn0XP3JMPnAKbQMftMO9WMubBGw2LZc,8129
|
|
4
5
|
gitcode_api/_exceptions.py,sha256=T5N8gBGmPSktDkLP5P_hxbzOHw3W378TzxN1xja40pA,1140
|
|
5
6
|
gitcode_api/_models.py,sha256=2bR6Wxc3Ie7iM3UFNY0ThWeNjxFHzDAC-8QFgcgcT_0,108599
|
|
6
|
-
gitcode_api/
|
|
7
|
+
gitcode_api/cli.py,sha256=R4HUPzrrs_ejHjIAEZw0P5ozUuEfFScJvfVRyrR2HXw,9573
|
|
8
|
+
gitcode_api/version.txt,sha256=QVh-oXE9O_Eto7b8aIGNtIm6f6enRYGYzqERAJEy8sk,5
|
|
7
9
|
gitcode_api/resources/__init__.py,sha256=nsCKW0bFDZ5ombJZxLThmO82sOuF7o4OKUMRkAmwbwk,1725
|
|
8
10
|
gitcode_api/resources/_shared.py,sha256=EB9fr4eQUc9IOdZCn4AoI946SJYCNVAtOOHEo_OzJAQ,5317
|
|
9
11
|
gitcode_api/resources/account.py,sha256=mnc2p7wI-nBnHFNdWPNiHfmZpT6d3RDQC777gewtm4M,38801
|
|
10
12
|
gitcode_api/resources/collaboration.py,sha256=26rQCeayZfUQ_6dIdl5HOGXnYkMLx8V7XsF_Wi-4Qt4,101417
|
|
11
13
|
gitcode_api/resources/misc.py,sha256=guDwh4cxbTVsSa7EivaYM3bKMJ8_Op4KucGbKEoayKE,22412
|
|
12
14
|
gitcode_api/resources/repositories.py,sha256=EAK2znZhEsgVUu-NDEQslSEEYJzvb-kHuh4mW57y6sc,78178
|
|
13
|
-
gitcode_api-1.1.
|
|
14
|
-
gitcode_api-1.1.
|
|
15
|
-
gitcode_api-1.1.
|
|
16
|
-
gitcode_api-1.1.
|
|
17
|
-
gitcode_api-1.1.
|
|
15
|
+
gitcode_api-1.1.3.dist-info/licenses/LICENSE,sha256=gOACXuWhMu6PJKVLr9RQbxX3HULnZIGNXCaMFJIXhoA,1067
|
|
16
|
+
gitcode_api-1.1.3.dist-info/METADATA,sha256=Cts4NepBSIfeffRrEec2u7jeHcrqmTCzWLIh-LE08mg,7715
|
|
17
|
+
gitcode_api-1.1.3.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
18
|
+
gitcode_api-1.1.3.dist-info/entry_points.txt,sha256=dIPylJcgohIE2RRIlt3In2WzcwDK8TOdkL_ReKuij4o,53
|
|
19
|
+
gitcode_api-1.1.3.dist-info/top_level.txt,sha256=gIlg0ptyOUHJT64ajOjWIhRPYgIQnMIvnhhnesw9fxU,12
|
|
20
|
+
gitcode_api-1.1.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|