omdev 0.0.0.dev211__py3-none-any.whl → 0.0.0.dev213__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.
- omdev/.manifests.json +15 -1
- omdev/__about__.py +0 -4
- omdev/amalg/gen.py +2 -3
- omdev/amalg/imports.py +4 -5
- omdev/amalg/manifests.py +7 -10
- omdev/amalg/resources.py +24 -27
- omdev/amalg/srcfiles.py +7 -10
- omdev/amalg/strip.py +4 -5
- omdev/amalg/types.py +1 -1
- omdev/amalg/typing.py +9 -8
- omdev/cc/cdeps.py +34 -1
- omdev/cc/cdeps.toml +19 -2
- omdev/cc/cli.py +13 -1
- omdev/ci/ci.py +71 -48
- omdev/ci/cli.py +22 -10
- omdev/ci/compose.py +30 -56
- omdev/ci/docker.py +35 -16
- omdev/ci/github/cache.py +153 -184
- omdev/ci/github/cacheapi.py +1 -1
- omdev/ci/github/cli.py +2 -2
- omdev/ci/github/curl.py +209 -0
- omdev/ci/requirements.py +2 -2
- omdev/git/shallow.py +1 -1
- omdev/scripts/ci.py +948 -451
- omdev/scripts/interp.py +23 -0
- omdev/scripts/pyproject.py +23 -0
- omdev/tokens/__init__.py +0 -0
- omdev/tokens/all.py +35 -0
- omdev/tokens/tokenizert.py +215 -0
- omdev/{tokens.py → tokens/utils.py} +6 -12
- omdev/tools/mkenv.py +131 -0
- omdev/tools/mkrelimp.py +4 -6
- {omdev-0.0.0.dev211.dist-info → omdev-0.0.0.dev213.dist-info}/METADATA +2 -5
- {omdev-0.0.0.dev211.dist-info → omdev-0.0.0.dev213.dist-info}/RECORD +38 -33
- {omdev-0.0.0.dev211.dist-info → omdev-0.0.0.dev213.dist-info}/LICENSE +0 -0
- {omdev-0.0.0.dev211.dist-info → omdev-0.0.0.dev213.dist-info}/WHEEL +0 -0
- {omdev-0.0.0.dev211.dist-info → omdev-0.0.0.dev213.dist-info}/entry_points.txt +0 -0
- {omdev-0.0.0.dev211.dist-info → omdev-0.0.0.dev213.dist-info}/top_level.txt +0 -0
omdev/ci/github/cache.py
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
# ruff: noqa: UP006 UP007
|
2
2
|
# @omlish-lite
|
3
|
+
import abc
|
3
4
|
import dataclasses as dc
|
4
|
-
import json
|
5
5
|
import os
|
6
6
|
import shlex
|
7
7
|
import typing as ta
|
8
|
+
import urllib.parse
|
8
9
|
|
9
10
|
from omlish.lite.check import check
|
10
|
-
from omlish.lite.contextmanagers import defer
|
11
|
-
from omlish.lite.json import json_dumps_compact
|
12
11
|
from omlish.subprocesses import subprocesses
|
13
12
|
|
14
13
|
from ..cache import DirectoryFileCache
|
@@ -16,256 +15,168 @@ from ..cache import ShellCache
|
|
16
15
|
from ..shell import ShellCmd
|
17
16
|
from ..utils import make_temp_file
|
18
17
|
from .cacheapi import GithubCacheServiceV1
|
18
|
+
from .curl import GithubServiceCurlClient
|
19
19
|
|
20
20
|
|
21
21
|
##
|
22
22
|
|
23
23
|
|
24
|
-
class
|
24
|
+
class GithubCacheShellClient(abc.ABC):
|
25
|
+
class Entry(abc.ABC): # noqa
|
26
|
+
pass
|
27
|
+
|
28
|
+
@abc.abstractmethod
|
29
|
+
def run_get_entry(self, key: str) -> ta.Optional[Entry]:
|
30
|
+
raise NotImplementedError
|
31
|
+
|
32
|
+
@abc.abstractmethod
|
33
|
+
def download_get_entry(self, entry: Entry, out_file: str) -> None:
|
34
|
+
raise NotImplementedError
|
35
|
+
|
36
|
+
@abc.abstractmethod
|
37
|
+
def upload_cache_entry(self, key: str, in_file: str) -> None:
|
38
|
+
raise NotImplementedError
|
39
|
+
|
40
|
+
|
41
|
+
#
|
42
|
+
|
43
|
+
|
44
|
+
class GithubCacheServiceV1ShellClient(GithubCacheShellClient):
|
25
45
|
BASE_URL_ENV_KEY = 'ACTIONS_CACHE_URL'
|
26
46
|
AUTH_TOKEN_ENV_KEY = 'ACTIONS_RUNTIME_TOKEN' # noqa
|
27
47
|
|
48
|
+
KEY_SUFFIX_ENV_KEY = 'GITHUB_RUN_ID'
|
49
|
+
|
50
|
+
CACHE_VERSION: ta.ClassVar[int] = 1
|
51
|
+
|
52
|
+
#
|
53
|
+
|
28
54
|
def __init__(
|
29
55
|
self,
|
30
56
|
*,
|
31
57
|
base_url: ta.Optional[str] = None,
|
32
58
|
auth_token: ta.Optional[str] = None,
|
59
|
+
|
60
|
+
key_prefix: ta.Optional[str] = None,
|
61
|
+
key_suffix: ta.Optional[str] = None,
|
33
62
|
) -> None:
|
34
63
|
super().__init__()
|
35
64
|
|
65
|
+
#
|
66
|
+
|
36
67
|
if base_url is None:
|
37
68
|
base_url = os.environ[self.BASE_URL_ENV_KEY]
|
38
|
-
|
69
|
+
service_url = GithubCacheServiceV1.get_service_url(base_url)
|
39
70
|
|
40
71
|
if auth_token is None:
|
41
72
|
auth_token = os.environ.get(self.AUTH_TOKEN_ENV_KEY)
|
42
|
-
self._auth_token = auth_token
|
43
|
-
|
44
|
-
self._service_url = GithubCacheServiceV1.get_service_url(self._base_url)
|
45
|
-
|
46
|
-
#
|
47
|
-
|
48
|
-
_MISSING = object()
|
49
73
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
) -> ta.Dict[str, str]:
|
56
|
-
dct = {
|
57
|
-
'Accept': f'application/json;api-version={GithubCacheServiceV1.API_VERSION}',
|
58
|
-
}
|
74
|
+
self._curl = GithubServiceCurlClient(
|
75
|
+
service_url,
|
76
|
+
auth_token,
|
77
|
+
api_version=GithubCacheServiceV1.API_VERSION,
|
78
|
+
)
|
59
79
|
|
60
|
-
|
61
|
-
auth_token = self._auth_token
|
62
|
-
if auth_token:
|
63
|
-
dct['Authorization'] = f'Bearer {auth_token}'
|
80
|
+
#
|
64
81
|
|
65
|
-
|
66
|
-
dct['Content-Type'] = content_type
|
82
|
+
self._key_prefix = key_prefix
|
67
83
|
|
68
|
-
|
84
|
+
if key_suffix is None:
|
85
|
+
key_suffix = os.environ[self.KEY_SUFFIX_ENV_KEY]
|
86
|
+
self._key_suffix = check.non_empty_str(key_suffix)
|
69
87
|
|
70
88
|
#
|
71
89
|
|
72
|
-
|
73
|
-
|
74
|
-
def build_curl_cmd(
|
75
|
-
self,
|
76
|
-
method: str,
|
77
|
-
url: str,
|
78
|
-
*,
|
79
|
-
json_content: bool = False,
|
80
|
-
content_type: ta.Optional[str] = None,
|
81
|
-
) -> ShellCmd:
|
82
|
-
if content_type is None and json_content:
|
83
|
-
content_type = 'application/json'
|
84
|
-
|
85
|
-
env = {}
|
86
|
-
|
87
|
-
header_auth_token: ta.Optional[str]
|
88
|
-
if self._auth_token:
|
89
|
-
env[self.HEADER_AUTH_TOKEN_ENV_KEY] = self._auth_token
|
90
|
-
header_auth_token = f'${self.HEADER_AUTH_TOKEN_ENV_KEY}'
|
91
|
-
else:
|
92
|
-
header_auth_token = None
|
93
|
-
|
94
|
-
hdrs = self.build_headers(
|
95
|
-
auth_token=header_auth_token,
|
96
|
-
content_type=content_type,
|
97
|
-
)
|
98
|
-
|
99
|
-
url = f'{self._service_url}/{url}'
|
90
|
+
KEY_PART_SEPARATOR = '--'
|
100
91
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
*[f'-H "{k}: {v}"' for k, v in hdrs.items()],
|
92
|
+
def fix_key(self, s: str) -> str:
|
93
|
+
return self.KEY_PART_SEPARATOR.join([
|
94
|
+
*([self._key_prefix] if self._key_prefix else []),
|
95
|
+
s,
|
96
|
+
self._key_suffix,
|
107
97
|
])
|
108
98
|
|
109
|
-
return ShellCmd(
|
110
|
-
cmd,
|
111
|
-
env=env,
|
112
|
-
)
|
113
|
-
|
114
|
-
def build_post_json_curl_cmd(
|
115
|
-
self,
|
116
|
-
url: str,
|
117
|
-
obj: ta.Any,
|
118
|
-
**kwargs: ta.Any,
|
119
|
-
) -> ShellCmd:
|
120
|
-
curl_cmd = self.build_curl_cmd(
|
121
|
-
'POST',
|
122
|
-
url,
|
123
|
-
json_content=True,
|
124
|
-
**kwargs,
|
125
|
-
)
|
126
|
-
|
127
|
-
obj_json = json_dumps_compact(obj)
|
128
|
-
|
129
|
-
return dc.replace(curl_cmd, s=f'{curl_cmd.s} -d {shlex.quote(obj_json)}')
|
130
|
-
|
131
99
|
#
|
132
100
|
|
133
|
-
@dc.dataclass()
|
134
|
-
class CurlError(RuntimeError):
|
135
|
-
status_code: int
|
136
|
-
body: ta.Optional[bytes]
|
137
|
-
|
138
|
-
def __str__(self) -> str:
|
139
|
-
return repr(self)
|
140
|
-
|
141
101
|
@dc.dataclass(frozen=True)
|
142
|
-
class
|
143
|
-
|
144
|
-
body: ta.Optional[bytes]
|
145
|
-
|
146
|
-
def as_error(self) -> 'GithubV1CacheShellClient.CurlError':
|
147
|
-
return GithubV1CacheShellClient.CurlError(
|
148
|
-
status_code=self.status_code,
|
149
|
-
body=self.body,
|
150
|
-
)
|
151
|
-
|
152
|
-
def run_curl_cmd(
|
153
|
-
self,
|
154
|
-
cmd: ShellCmd,
|
155
|
-
*,
|
156
|
-
raise_: bool = False,
|
157
|
-
) -> CurlResult:
|
158
|
-
out_file = make_temp_file()
|
159
|
-
with defer(lambda: os.unlink(out_file)):
|
160
|
-
run_cmd = dc.replace(cmd, s=f"{cmd.s} -o {out_file} -w '%{{json}}'")
|
161
|
-
|
162
|
-
out_json_bytes = run_cmd.run(subprocesses.check_output)
|
163
|
-
|
164
|
-
out_json = json.loads(out_json_bytes.decode())
|
165
|
-
status_code = check.isinstance(out_json['response_code'], int)
|
166
|
-
|
167
|
-
with open(out_file, 'rb') as f:
|
168
|
-
body = f.read()
|
169
|
-
|
170
|
-
result = self.CurlResult(
|
171
|
-
status_code=status_code,
|
172
|
-
body=body,
|
173
|
-
)
|
174
|
-
|
175
|
-
if raise_ and (500 <= status_code <= 600):
|
176
|
-
raise result.as_error()
|
177
|
-
|
178
|
-
return result
|
179
|
-
|
180
|
-
def run_json_curl_cmd(
|
181
|
-
self,
|
182
|
-
cmd: ShellCmd,
|
183
|
-
*,
|
184
|
-
success_status_codes: ta.Optional[ta.Container[int]] = None,
|
185
|
-
) -> ta.Optional[ta.Any]:
|
186
|
-
result = self.run_curl_cmd(cmd, raise_=True)
|
187
|
-
|
188
|
-
if success_status_codes is not None:
|
189
|
-
is_success = result.status_code in success_status_codes
|
190
|
-
else:
|
191
|
-
is_success = 200 <= result.status_code < 300
|
192
|
-
|
193
|
-
if is_success:
|
194
|
-
if not (body := result.body):
|
195
|
-
return None
|
196
|
-
return json.loads(body.decode('utf-8-sig'))
|
197
|
-
|
198
|
-
elif result.status_code == 404:
|
199
|
-
return None
|
200
|
-
|
201
|
-
else:
|
202
|
-
raise result.as_error()
|
102
|
+
class Entry(GithubCacheShellClient.Entry):
|
103
|
+
artifact: GithubCacheServiceV1.ArtifactCacheEntry
|
203
104
|
|
204
105
|
#
|
205
106
|
|
206
107
|
def build_get_entry_curl_cmd(self, key: str) -> ShellCmd:
|
207
|
-
|
108
|
+
fixed_key = self.fix_key(key)
|
109
|
+
|
110
|
+
qp = dict(
|
111
|
+
keys=fixed_key,
|
112
|
+
version=str(self.CACHE_VERSION),
|
113
|
+
)
|
114
|
+
|
115
|
+
return self._curl.build_cmd(
|
208
116
|
'GET',
|
209
|
-
|
117
|
+
shlex.quote('?'.join([
|
118
|
+
'cache',
|
119
|
+
'&'.join([
|
120
|
+
f'{k}={urllib.parse.quote_plus(v)}'
|
121
|
+
for k, v in qp.items()
|
122
|
+
]),
|
123
|
+
])),
|
210
124
|
)
|
211
125
|
|
212
|
-
def run_get_entry(self, key: str) -> ta.Optional[
|
213
|
-
|
126
|
+
def run_get_entry(self, key: str) -> ta.Optional[Entry]:
|
127
|
+
fixed_key = self.fix_key(key)
|
128
|
+
curl_cmd = self.build_get_entry_curl_cmd(fixed_key)
|
214
129
|
|
215
|
-
obj = self.
|
130
|
+
obj = self._curl.run_json_cmd(
|
216
131
|
curl_cmd,
|
217
132
|
success_status_codes=[200, 204],
|
218
133
|
)
|
219
134
|
if obj is None:
|
220
135
|
return None
|
221
136
|
|
222
|
-
return GithubCacheServiceV1.dataclass_from_json(
|
137
|
+
return self.Entry(GithubCacheServiceV1.dataclass_from_json(
|
223
138
|
GithubCacheServiceV1.ArtifactCacheEntry,
|
224
139
|
obj,
|
225
|
-
)
|
140
|
+
))
|
226
141
|
|
227
142
|
#
|
228
143
|
|
229
|
-
def build_download_get_entry_cmd(
|
230
|
-
self,
|
231
|
-
entry: GithubCacheServiceV1.ArtifactCacheEntry,
|
232
|
-
out_file: str,
|
233
|
-
) -> ShellCmd:
|
144
|
+
def build_download_get_entry_cmd(self, entry: Entry, out_file: str) -> ShellCmd:
|
234
145
|
return ShellCmd(' '.join([
|
235
146
|
'aria2c',
|
236
147
|
'-x', '4',
|
237
148
|
'-o', out_file,
|
238
|
-
check.non_empty_str(entry.archive_location),
|
149
|
+
check.non_empty_str(entry.artifact.archive_location),
|
239
150
|
]))
|
240
151
|
|
241
|
-
def download_get_entry(
|
242
|
-
|
243
|
-
entry
|
244
|
-
out_file
|
245
|
-
|
246
|
-
dl_cmd = self.build_download_get_entry_cmd(entry, out_file)
|
152
|
+
def download_get_entry(self, entry: GithubCacheShellClient.Entry, out_file: str) -> None:
|
153
|
+
dl_cmd = self.build_download_get_entry_cmd(
|
154
|
+
check.isinstance(entry, GithubCacheServiceV1ShellClient.Entry),
|
155
|
+
out_file,
|
156
|
+
)
|
247
157
|
dl_cmd.run(subprocesses.check_call)
|
248
158
|
|
249
159
|
#
|
250
160
|
|
251
|
-
def upload_cache_entry(
|
252
|
-
|
253
|
-
|
254
|
-
in_file: str,
|
255
|
-
) -> None:
|
161
|
+
def upload_cache_entry(self, key: str, in_file: str) -> None:
|
162
|
+
fixed_key = self.fix_key(key)
|
163
|
+
|
256
164
|
check.state(os.path.isfile(in_file))
|
257
165
|
|
258
166
|
file_size = os.stat(in_file).st_size
|
259
167
|
|
168
|
+
#
|
169
|
+
|
260
170
|
reserve_req = GithubCacheServiceV1.ReserveCacheRequest(
|
261
|
-
key=
|
171
|
+
key=fixed_key,
|
262
172
|
cache_size=file_size,
|
173
|
+
version=str(self.CACHE_VERSION),
|
263
174
|
)
|
264
|
-
reserve_cmd = self.
|
175
|
+
reserve_cmd = self._curl.build_post_json_cmd(
|
265
176
|
'caches',
|
266
177
|
GithubCacheServiceV1.dataclass_to_json(reserve_req),
|
267
178
|
)
|
268
|
-
reserve_resp_obj: ta.Any = check.not_none(self.
|
179
|
+
reserve_resp_obj: ta.Any = check.not_none(self._curl.run_json_cmd(
|
269
180
|
reserve_cmd,
|
270
181
|
success_status_codes=[201],
|
271
182
|
))
|
@@ -273,8 +184,66 @@ class GithubV1CacheShellClient:
|
|
273
184
|
GithubCacheServiceV1.ReserveCacheResponse,
|
274
185
|
reserve_resp_obj,
|
275
186
|
)
|
187
|
+
cache_id = check.isinstance(reserve_resp.cache_id, int)
|
188
|
+
|
189
|
+
#
|
190
|
+
|
191
|
+
tmp_file = make_temp_file()
|
192
|
+
|
193
|
+
print(f'{file_size=}')
|
194
|
+
num_written = 0
|
195
|
+
chunk_size = 32 * 1024 * 1024
|
196
|
+
for i in range((file_size // chunk_size) + (1 if file_size % chunk_size else 0)):
|
197
|
+
ofs = i * chunk_size
|
198
|
+
sz = min(chunk_size, file_size - ofs)
|
199
|
+
|
200
|
+
patch_cmd = self._curl.build_cmd(
|
201
|
+
'PATCH',
|
202
|
+
f'caches/{cache_id}',
|
203
|
+
content_type='application/octet-stream',
|
204
|
+
headers={
|
205
|
+
'Content-Range': f'bytes {ofs}-{ofs + sz - 1}/*',
|
206
|
+
},
|
207
|
+
)
|
276
208
|
|
277
|
-
|
209
|
+
#
|
210
|
+
|
211
|
+
# patch_data_cmd = dc.replace(patch_cmd, s=' | '.join([
|
212
|
+
# f'dd if={in_file} bs={chunk_size} skip={i} count=1 status=none',
|
213
|
+
# f'{patch_cmd.s} --data-binary -',
|
214
|
+
# ]))
|
215
|
+
# print(f'{patch_data_cmd.s=}')
|
216
|
+
# patch_result = self._curl.run_cmd(patch_data_cmd, raise_=True)
|
217
|
+
|
218
|
+
#
|
219
|
+
|
220
|
+
with open(in_file, 'rb') as f:
|
221
|
+
f.seek(ofs)
|
222
|
+
buf = f.read(sz)
|
223
|
+
with open(tmp_file, 'wb') as f:
|
224
|
+
f.write(buf)
|
225
|
+
num_written += len(buf)
|
226
|
+
print(f'{num_written=}')
|
227
|
+
patch_data_cmd = dc.replace(patch_cmd, s=f'{patch_cmd.s} --data-binary @{tmp_file}')
|
228
|
+
print(f'{patch_data_cmd.s=}')
|
229
|
+
patch_result = self._curl.run_cmd(patch_data_cmd, raise_=True)
|
230
|
+
|
231
|
+
#
|
232
|
+
|
233
|
+
check.equal(patch_result.status_code, 204)
|
234
|
+
ofs += sz
|
235
|
+
|
236
|
+
#
|
237
|
+
|
238
|
+
commit_req = GithubCacheServiceV1.CommitCacheRequest(
|
239
|
+
size=file_size,
|
240
|
+
)
|
241
|
+
commit_cmd = self._curl.build_post_json_cmd(
|
242
|
+
f'caches/{cache_id}',
|
243
|
+
GithubCacheServiceV1.dataclass_to_json(commit_req),
|
244
|
+
)
|
245
|
+
commit_result = self._curl.run_cmd(commit_cmd, raise_=True)
|
246
|
+
check.equal(commit_result.status_code, 204)
|
278
247
|
|
279
248
|
|
280
249
|
##
|
@@ -285,15 +254,15 @@ class GithubShellCache(ShellCache):
|
|
285
254
|
self,
|
286
255
|
dir: str, # noqa
|
287
256
|
*,
|
288
|
-
client: ta.Optional[
|
257
|
+
client: ta.Optional[GithubCacheShellClient] = None,
|
289
258
|
) -> None:
|
290
259
|
super().__init__()
|
291
260
|
|
292
261
|
self._dir = check.not_none(dir)
|
293
262
|
|
294
263
|
if client is None:
|
295
|
-
client =
|
296
|
-
self._client = client
|
264
|
+
client = GithubCacheServiceV1ShellClient()
|
265
|
+
self._client: GithubCacheShellClient = client
|
297
266
|
|
298
267
|
self._local = DirectoryFileCache(self._dir)
|
299
268
|
|
omdev/ci/github/cacheapi.py
CHANGED
omdev/ci/github/cli.py
CHANGED
@@ -11,7 +11,7 @@ from omlish.argparse.cli import argparse_arg
|
|
11
11
|
from omlish.argparse.cli import argparse_cmd
|
12
12
|
from omlish.lite.json import json_dumps_pretty
|
13
13
|
|
14
|
-
from .cache import
|
14
|
+
from .cache import GithubCacheServiceV1ShellClient
|
15
15
|
|
16
16
|
|
17
17
|
class GithubCli(ArgparseCli):
|
@@ -19,7 +19,7 @@ class GithubCli(ArgparseCli):
|
|
19
19
|
argparse_arg('key'),
|
20
20
|
)
|
21
21
|
def get_cache_entry(self) -> None:
|
22
|
-
shell_client =
|
22
|
+
shell_client = GithubCacheServiceV1ShellClient()
|
23
23
|
entry = shell_client.run_get_entry(self.args.key)
|
24
24
|
if entry is None:
|
25
25
|
return
|
omdev/ci/github/curl.py
ADDED
@@ -0,0 +1,209 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
# @omlish-lite
|
3
|
+
import dataclasses as dc
|
4
|
+
import json
|
5
|
+
import os
|
6
|
+
import shlex
|
7
|
+
import typing as ta
|
8
|
+
|
9
|
+
from omlish.lite.check import check
|
10
|
+
from omlish.lite.contextmanagers import defer
|
11
|
+
from omlish.lite.json import json_dumps_compact
|
12
|
+
from omlish.subprocesses import subprocesses
|
13
|
+
|
14
|
+
from ..shell import ShellCmd
|
15
|
+
from ..utils import make_temp_file
|
16
|
+
|
17
|
+
|
18
|
+
##
|
19
|
+
|
20
|
+
|
21
|
+
class GithubServiceCurlClient:
|
22
|
+
def __init__(
|
23
|
+
self,
|
24
|
+
service_url: str,
|
25
|
+
auth_token: ta.Optional[str] = None,
|
26
|
+
*,
|
27
|
+
api_version: ta.Optional[str] = None,
|
28
|
+
) -> None:
|
29
|
+
super().__init__()
|
30
|
+
|
31
|
+
self._service_url = check.non_empty_str(service_url)
|
32
|
+
self._auth_token = auth_token
|
33
|
+
self._api_version = api_version
|
34
|
+
|
35
|
+
#
|
36
|
+
|
37
|
+
_MISSING = object()
|
38
|
+
|
39
|
+
def build_headers(
|
40
|
+
self,
|
41
|
+
headers: ta.Optional[ta.Mapping[str, str]] = None,
|
42
|
+
*,
|
43
|
+
auth_token: ta.Any = _MISSING,
|
44
|
+
content_type: ta.Optional[str] = None,
|
45
|
+
) -> ta.Dict[str, str]:
|
46
|
+
dct = {
|
47
|
+
'Accept': ';'.join([
|
48
|
+
'application/json',
|
49
|
+
*([f'api-version={self._api_version}'] if self._api_version else []),
|
50
|
+
]),
|
51
|
+
}
|
52
|
+
|
53
|
+
if auth_token is self._MISSING:
|
54
|
+
auth_token = self._auth_token
|
55
|
+
if auth_token:
|
56
|
+
dct['Authorization'] = f'Bearer {auth_token}'
|
57
|
+
|
58
|
+
if content_type is not None:
|
59
|
+
dct['Content-Type'] = content_type
|
60
|
+
|
61
|
+
if headers:
|
62
|
+
dct.update(headers)
|
63
|
+
|
64
|
+
return dct
|
65
|
+
|
66
|
+
#
|
67
|
+
|
68
|
+
HEADER_AUTH_TOKEN_ENV_KEY_PREFIX = '_GITHUB_SERVICE_AUTH_TOKEN' # noqa
|
69
|
+
|
70
|
+
@property
|
71
|
+
def header_auth_token_env_key(self) -> str:
|
72
|
+
return f'{self.HEADER_AUTH_TOKEN_ENV_KEY_PREFIX}_{id(self)}'
|
73
|
+
|
74
|
+
def build_cmd(
|
75
|
+
self,
|
76
|
+
method: str,
|
77
|
+
url: str,
|
78
|
+
*,
|
79
|
+
json_content: bool = False,
|
80
|
+
content_type: ta.Optional[str] = None,
|
81
|
+
headers: ta.Optional[ta.Dict[str, str]] = None,
|
82
|
+
) -> ShellCmd:
|
83
|
+
if content_type is None and json_content:
|
84
|
+
content_type = 'application/json'
|
85
|
+
|
86
|
+
env = {}
|
87
|
+
|
88
|
+
header_auth_token: ta.Optional[str]
|
89
|
+
if self._auth_token:
|
90
|
+
header_env_key = self.header_auth_token_env_key
|
91
|
+
env[header_env_key] = self._auth_token
|
92
|
+
header_auth_token = f'${header_env_key}'
|
93
|
+
else:
|
94
|
+
header_auth_token = None
|
95
|
+
|
96
|
+
built_hdrs = self.build_headers(
|
97
|
+
headers,
|
98
|
+
auth_token=header_auth_token,
|
99
|
+
content_type=content_type,
|
100
|
+
)
|
101
|
+
|
102
|
+
url = f'{self._service_url}/{url}'
|
103
|
+
|
104
|
+
cmd = ' '.join([
|
105
|
+
'curl',
|
106
|
+
'-s',
|
107
|
+
'-X', method,
|
108
|
+
url,
|
109
|
+
*[f'-H "{k}: {v}"' for k, v in built_hdrs.items()],
|
110
|
+
])
|
111
|
+
|
112
|
+
return ShellCmd(
|
113
|
+
cmd,
|
114
|
+
env=env,
|
115
|
+
)
|
116
|
+
|
117
|
+
def build_post_json_cmd(
|
118
|
+
self,
|
119
|
+
url: str,
|
120
|
+
obj: ta.Any,
|
121
|
+
**kwargs: ta.Any,
|
122
|
+
) -> ShellCmd:
|
123
|
+
curl_cmd = self.build_cmd(
|
124
|
+
'POST',
|
125
|
+
url,
|
126
|
+
json_content=True,
|
127
|
+
**kwargs,
|
128
|
+
)
|
129
|
+
|
130
|
+
obj_json = json_dumps_compact(obj)
|
131
|
+
|
132
|
+
return dc.replace(curl_cmd, s=f'{curl_cmd.s} -d {shlex.quote(obj_json)}')
|
133
|
+
|
134
|
+
#
|
135
|
+
|
136
|
+
@dc.dataclass()
|
137
|
+
class Error(RuntimeError):
|
138
|
+
status_code: int
|
139
|
+
body: ta.Optional[bytes]
|
140
|
+
|
141
|
+
def __str__(self) -> str:
|
142
|
+
return repr(self)
|
143
|
+
|
144
|
+
@dc.dataclass(frozen=True)
|
145
|
+
class Result:
|
146
|
+
status_code: int
|
147
|
+
body: ta.Optional[bytes]
|
148
|
+
|
149
|
+
def as_error(self) -> 'GithubServiceCurlClient.Error':
|
150
|
+
return GithubServiceCurlClient.Error(
|
151
|
+
status_code=self.status_code,
|
152
|
+
body=self.body,
|
153
|
+
)
|
154
|
+
|
155
|
+
def run_cmd(
|
156
|
+
self,
|
157
|
+
cmd: ShellCmd,
|
158
|
+
*,
|
159
|
+
raise_: bool = False,
|
160
|
+
**subprocess_kwargs: ta.Any,
|
161
|
+
) -> Result:
|
162
|
+
out_file = make_temp_file()
|
163
|
+
with defer(lambda: os.unlink(out_file)):
|
164
|
+
run_cmd = dc.replace(cmd, s=f"{cmd.s} -o {out_file} -w '%{{json}}'")
|
165
|
+
|
166
|
+
out_json_bytes = run_cmd.run(
|
167
|
+
subprocesses.check_output,
|
168
|
+
**subprocess_kwargs,
|
169
|
+
)
|
170
|
+
|
171
|
+
out_json = json.loads(out_json_bytes.decode())
|
172
|
+
status_code = check.isinstance(out_json['response_code'], int)
|
173
|
+
|
174
|
+
with open(out_file, 'rb') as f:
|
175
|
+
body = f.read()
|
176
|
+
|
177
|
+
result = self.Result(
|
178
|
+
status_code=status_code,
|
179
|
+
body=body,
|
180
|
+
)
|
181
|
+
|
182
|
+
if raise_ and (500 <= status_code <= 600):
|
183
|
+
raise result.as_error()
|
184
|
+
|
185
|
+
return result
|
186
|
+
|
187
|
+
def run_json_cmd(
|
188
|
+
self,
|
189
|
+
cmd: ShellCmd,
|
190
|
+
*,
|
191
|
+
success_status_codes: ta.Optional[ta.Container[int]] = None,
|
192
|
+
) -> ta.Optional[ta.Any]:
|
193
|
+
result = self.run_cmd(cmd, raise_=True)
|
194
|
+
|
195
|
+
if success_status_codes is not None:
|
196
|
+
is_success = result.status_code in success_status_codes
|
197
|
+
else:
|
198
|
+
is_success = 200 <= result.status_code < 300
|
199
|
+
|
200
|
+
if is_success:
|
201
|
+
if not (body := result.body):
|
202
|
+
return None
|
203
|
+
return json.loads(body.decode('utf-8-sig'))
|
204
|
+
|
205
|
+
elif result.status_code == 404:
|
206
|
+
return None
|
207
|
+
|
208
|
+
else:
|
209
|
+
raise result.as_error()
|
omdev/ci/requirements.py
CHANGED
omdev/git/shallow.py
CHANGED