ominfra 0.0.0.dev145__py3-none-any.whl → 0.0.0.dev147__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.
- ominfra/manage/commands/inject.py +5 -0
- ominfra/manage/commands/interp.py +39 -0
- ominfra/manage/commands/subprocess.py +2 -2
- ominfra/manage/deploy/command.py +4 -0
- ominfra/manage/deploy/paths.py +147 -0
- ominfra/manage/main.py +14 -8
- ominfra/manage/remote/channel.py +13 -2
- ominfra/manage/remote/execution.py +64 -8
- ominfra/scripts/manage.py +2195 -236
- {ominfra-0.0.0.dev145.dist-info → ominfra-0.0.0.dev147.dist-info}/METADATA +3 -3
- {ominfra-0.0.0.dev145.dist-info → ominfra-0.0.0.dev147.dist-info}/RECORD +15 -14
- {ominfra-0.0.0.dev145.dist-info → ominfra-0.0.0.dev147.dist-info}/LICENSE +0 -0
- {ominfra-0.0.0.dev145.dist-info → ominfra-0.0.0.dev147.dist-info}/WHEEL +0 -0
- {ominfra-0.0.0.dev145.dist-info → ominfra-0.0.0.dev147.dist-info}/entry_points.txt +0 -0
- {ominfra-0.0.0.dev145.dist-info → ominfra-0.0.0.dev147.dist-info}/top_level.txt +0 -0
ominfra/scripts/manage.py
CHANGED
@@ -10,6 +10,7 @@ manage.py -s 'ssh -i /foo/bar.pem foo@bar.baz' -q --python=python3.8
|
|
10
10
|
"""
|
11
11
|
import abc
|
12
12
|
import base64
|
13
|
+
import collections
|
13
14
|
import collections.abc
|
14
15
|
import contextlib
|
15
16
|
import dataclasses as dc
|
@@ -19,12 +20,16 @@ import enum
|
|
19
20
|
import fractions
|
20
21
|
import functools
|
21
22
|
import inspect
|
23
|
+
import itertools
|
22
24
|
import json
|
23
25
|
import logging
|
24
26
|
import os
|
27
|
+
import os.path
|
25
28
|
import platform
|
26
29
|
import pwd
|
30
|
+
import re
|
27
31
|
import shlex
|
32
|
+
import shutil
|
28
33
|
import site
|
29
34
|
import struct
|
30
35
|
import subprocess
|
@@ -49,6 +54,14 @@ if sys.version_info < (3, 8):
|
|
49
54
|
########################################
|
50
55
|
|
51
56
|
|
57
|
+
# ../../omdev/packaging/versions.py
|
58
|
+
VersionLocalType = ta.Tuple[ta.Union[int, str], ...]
|
59
|
+
VersionCmpPrePostDevType = ta.Union['InfinityVersionType', 'NegativeInfinityVersionType', ta.Tuple[str, int]]
|
60
|
+
_VersionCmpLocalType0 = ta.Tuple[ta.Union[ta.Tuple[int, str], ta.Tuple['NegativeInfinityVersionType', ta.Union[int, str]]], ...] # noqa
|
61
|
+
VersionCmpLocalType = ta.Union['NegativeInfinityVersionType', _VersionCmpLocalType0]
|
62
|
+
VersionCmpKey = ta.Tuple[int, ta.Tuple[int, ...], VersionCmpPrePostDevType, VersionCmpPrePostDevType, VersionCmpPrePostDevType, VersionCmpLocalType] # noqa
|
63
|
+
VersionComparisonMethod = ta.Callable[[VersionCmpKey, VersionCmpKey], bool]
|
64
|
+
|
52
65
|
# ../../omlish/lite/cached.py
|
53
66
|
T = ta.TypeVar('T')
|
54
67
|
CallableT = ta.TypeVar('CallableT', bound=ta.Callable)
|
@@ -56,6 +69,11 @@ CallableT = ta.TypeVar('CallableT', bound=ta.Callable)
|
|
56
69
|
# ../../omlish/lite/check.py
|
57
70
|
SizedT = ta.TypeVar('SizedT', bound=ta.Sized)
|
58
71
|
|
72
|
+
# ../../omdev/packaging/specifiers.py
|
73
|
+
UnparsedVersion = ta.Union['Version', str]
|
74
|
+
UnparsedVersionVar = ta.TypeVar('UnparsedVersionVar', bound=UnparsedVersion)
|
75
|
+
CallableVersionOperator = ta.Callable[['Version', str], bool]
|
76
|
+
|
59
77
|
# commands/base.py
|
60
78
|
CommandT = ta.TypeVar('CommandT', bound='Command')
|
61
79
|
CommandOutputT = ta.TypeVar('CommandOutputT', bound='Command.Output')
|
@@ -71,6 +89,413 @@ InjectorBindingOrBindings = ta.Union['InjectorBinding', 'InjectorBindings']
|
|
71
89
|
SubprocessChannelOption = ta.Literal['pipe', 'stdout', 'devnull']
|
72
90
|
|
73
91
|
|
92
|
+
########################################
|
93
|
+
# ../../../omdev/packaging/versions.py
|
94
|
+
# Copyright (c) Donald Stufft and individual contributors.
|
95
|
+
# All rights reserved.
|
96
|
+
#
|
97
|
+
# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
|
98
|
+
# following conditions are met:
|
99
|
+
#
|
100
|
+
# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the
|
101
|
+
# following disclaimer.
|
102
|
+
#
|
103
|
+
# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
|
104
|
+
# following disclaimer in the documentation and/or other materials provided with the distribution.
|
105
|
+
#
|
106
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
107
|
+
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
108
|
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
109
|
+
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
110
|
+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
111
|
+
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
112
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. This file is dual licensed under the terms of the
|
113
|
+
# Apache License, Version 2.0, and the BSD License. See the LICENSE file in the root of this repository for complete
|
114
|
+
# details.
|
115
|
+
# https://github.com/pypa/packaging/blob/2c885fe91a54559e2382902dce28428ad2887be5/src/packaging/version.py
|
116
|
+
|
117
|
+
|
118
|
+
##
|
119
|
+
|
120
|
+
|
121
|
+
class InfinityVersionType:
|
122
|
+
def __repr__(self) -> str:
|
123
|
+
return 'Infinity'
|
124
|
+
|
125
|
+
def __hash__(self) -> int:
|
126
|
+
return hash(repr(self))
|
127
|
+
|
128
|
+
def __lt__(self, other: object) -> bool:
|
129
|
+
return False
|
130
|
+
|
131
|
+
def __le__(self, other: object) -> bool:
|
132
|
+
return False
|
133
|
+
|
134
|
+
def __eq__(self, other: object) -> bool:
|
135
|
+
return isinstance(other, self.__class__)
|
136
|
+
|
137
|
+
def __gt__(self, other: object) -> bool:
|
138
|
+
return True
|
139
|
+
|
140
|
+
def __ge__(self, other: object) -> bool:
|
141
|
+
return True
|
142
|
+
|
143
|
+
def __neg__(self: object) -> 'NegativeInfinityVersionType':
|
144
|
+
return NegativeInfinityVersion
|
145
|
+
|
146
|
+
|
147
|
+
InfinityVersion = InfinityVersionType()
|
148
|
+
|
149
|
+
|
150
|
+
class NegativeInfinityVersionType:
|
151
|
+
def __repr__(self) -> str:
|
152
|
+
return '-Infinity'
|
153
|
+
|
154
|
+
def __hash__(self) -> int:
|
155
|
+
return hash(repr(self))
|
156
|
+
|
157
|
+
def __lt__(self, other: object) -> bool:
|
158
|
+
return True
|
159
|
+
|
160
|
+
def __le__(self, other: object) -> bool:
|
161
|
+
return True
|
162
|
+
|
163
|
+
def __eq__(self, other: object) -> bool:
|
164
|
+
return isinstance(other, self.__class__)
|
165
|
+
|
166
|
+
def __gt__(self, other: object) -> bool:
|
167
|
+
return False
|
168
|
+
|
169
|
+
def __ge__(self, other: object) -> bool:
|
170
|
+
return False
|
171
|
+
|
172
|
+
def __neg__(self: object) -> InfinityVersionType:
|
173
|
+
return InfinityVersion
|
174
|
+
|
175
|
+
|
176
|
+
NegativeInfinityVersion = NegativeInfinityVersionType()
|
177
|
+
|
178
|
+
|
179
|
+
##
|
180
|
+
|
181
|
+
|
182
|
+
class _Version(ta.NamedTuple):
|
183
|
+
epoch: int
|
184
|
+
release: ta.Tuple[int, ...]
|
185
|
+
dev: ta.Optional[ta.Tuple[str, int]]
|
186
|
+
pre: ta.Optional[ta.Tuple[str, int]]
|
187
|
+
post: ta.Optional[ta.Tuple[str, int]]
|
188
|
+
local: ta.Optional[VersionLocalType]
|
189
|
+
|
190
|
+
|
191
|
+
class InvalidVersion(ValueError): # noqa
|
192
|
+
pass
|
193
|
+
|
194
|
+
|
195
|
+
class _BaseVersion:
|
196
|
+
_key: ta.Tuple[ta.Any, ...]
|
197
|
+
|
198
|
+
def __hash__(self) -> int:
|
199
|
+
return hash(self._key)
|
200
|
+
|
201
|
+
def __lt__(self, other: '_BaseVersion') -> bool:
|
202
|
+
if not isinstance(other, _BaseVersion):
|
203
|
+
return NotImplemented # type: ignore
|
204
|
+
return self._key < other._key
|
205
|
+
|
206
|
+
def __le__(self, other: '_BaseVersion') -> bool:
|
207
|
+
if not isinstance(other, _BaseVersion):
|
208
|
+
return NotImplemented # type: ignore
|
209
|
+
return self._key <= other._key
|
210
|
+
|
211
|
+
def __eq__(self, other: object) -> bool:
|
212
|
+
if not isinstance(other, _BaseVersion):
|
213
|
+
return NotImplemented
|
214
|
+
return self._key == other._key
|
215
|
+
|
216
|
+
def __ge__(self, other: '_BaseVersion') -> bool:
|
217
|
+
if not isinstance(other, _BaseVersion):
|
218
|
+
return NotImplemented # type: ignore
|
219
|
+
return self._key >= other._key
|
220
|
+
|
221
|
+
def __gt__(self, other: '_BaseVersion') -> bool:
|
222
|
+
if not isinstance(other, _BaseVersion):
|
223
|
+
return NotImplemented # type: ignore
|
224
|
+
return self._key > other._key
|
225
|
+
|
226
|
+
def __ne__(self, other: object) -> bool:
|
227
|
+
if not isinstance(other, _BaseVersion):
|
228
|
+
return NotImplemented
|
229
|
+
return self._key != other._key
|
230
|
+
|
231
|
+
|
232
|
+
_VERSION_PATTERN = r"""
|
233
|
+
v?
|
234
|
+
(?:
|
235
|
+
(?:(?P<epoch>[0-9]+)!)?
|
236
|
+
(?P<release>[0-9]+(?:\.[0-9]+)*)
|
237
|
+
(?P<pre>
|
238
|
+
[-_\.]?
|
239
|
+
(?P<pre_l>alpha|a|beta|b|preview|pre|c|rc)
|
240
|
+
[-_\.]?
|
241
|
+
(?P<pre_n>[0-9]+)?
|
242
|
+
)?
|
243
|
+
(?P<post>
|
244
|
+
(?:-(?P<post_n1>[0-9]+))
|
245
|
+
|
|
246
|
+
(?:
|
247
|
+
[-_\.]?
|
248
|
+
(?P<post_l>post|rev|r)
|
249
|
+
[-_\.]?
|
250
|
+
(?P<post_n2>[0-9]+)?
|
251
|
+
)
|
252
|
+
)?
|
253
|
+
(?P<dev>
|
254
|
+
[-_\.]?
|
255
|
+
(?P<dev_l>dev)
|
256
|
+
[-_\.]?
|
257
|
+
(?P<dev_n>[0-9]+)?
|
258
|
+
)?
|
259
|
+
)
|
260
|
+
(?:\+(?P<local>[a-z0-9]+(?:[-_\.][a-z0-9]+)*))?
|
261
|
+
"""
|
262
|
+
|
263
|
+
VERSION_PATTERN = _VERSION_PATTERN
|
264
|
+
|
265
|
+
|
266
|
+
class Version(_BaseVersion):
|
267
|
+
_regex = re.compile(r'^\s*' + VERSION_PATTERN + r'\s*$', re.VERBOSE | re.IGNORECASE)
|
268
|
+
_key: VersionCmpKey
|
269
|
+
|
270
|
+
def __init__(self, version: str) -> None:
|
271
|
+
match = self._regex.search(version)
|
272
|
+
if not match:
|
273
|
+
raise InvalidVersion(f"Invalid version: '{version}'")
|
274
|
+
|
275
|
+
self._version = _Version(
|
276
|
+
epoch=int(match.group('epoch')) if match.group('epoch') else 0,
|
277
|
+
release=tuple(int(i) for i in match.group('release').split('.')),
|
278
|
+
pre=_parse_letter_version(match.group('pre_l'), match.group('pre_n')),
|
279
|
+
post=_parse_letter_version(match.group('post_l'), match.group('post_n1') or match.group('post_n2')),
|
280
|
+
dev=_parse_letter_version(match.group('dev_l'), match.group('dev_n')),
|
281
|
+
local=_parse_local_version(match.group('local')),
|
282
|
+
)
|
283
|
+
|
284
|
+
self._key = _version_cmpkey(
|
285
|
+
self._version.epoch,
|
286
|
+
self._version.release,
|
287
|
+
self._version.pre,
|
288
|
+
self._version.post,
|
289
|
+
self._version.dev,
|
290
|
+
self._version.local,
|
291
|
+
)
|
292
|
+
|
293
|
+
def __repr__(self) -> str:
|
294
|
+
return f"<Version('{self}')>"
|
295
|
+
|
296
|
+
def __str__(self) -> str:
|
297
|
+
parts = []
|
298
|
+
|
299
|
+
if self.epoch != 0:
|
300
|
+
parts.append(f'{self.epoch}!')
|
301
|
+
|
302
|
+
parts.append('.'.join(str(x) for x in self.release))
|
303
|
+
|
304
|
+
if self.pre is not None:
|
305
|
+
parts.append(''.join(str(x) for x in self.pre))
|
306
|
+
|
307
|
+
if self.post is not None:
|
308
|
+
parts.append(f'.post{self.post}')
|
309
|
+
|
310
|
+
if self.dev is not None:
|
311
|
+
parts.append(f'.dev{self.dev}')
|
312
|
+
|
313
|
+
if self.local is not None:
|
314
|
+
parts.append(f'+{self.local}')
|
315
|
+
|
316
|
+
return ''.join(parts)
|
317
|
+
|
318
|
+
@property
|
319
|
+
def epoch(self) -> int:
|
320
|
+
return self._version.epoch
|
321
|
+
|
322
|
+
@property
|
323
|
+
def release(self) -> ta.Tuple[int, ...]:
|
324
|
+
return self._version.release
|
325
|
+
|
326
|
+
@property
|
327
|
+
def pre(self) -> ta.Optional[ta.Tuple[str, int]]:
|
328
|
+
return self._version.pre
|
329
|
+
|
330
|
+
@property
|
331
|
+
def post(self) -> ta.Optional[int]:
|
332
|
+
return self._version.post[1] if self._version.post else None
|
333
|
+
|
334
|
+
@property
|
335
|
+
def dev(self) -> ta.Optional[int]:
|
336
|
+
return self._version.dev[1] if self._version.dev else None
|
337
|
+
|
338
|
+
@property
|
339
|
+
def local(self) -> ta.Optional[str]:
|
340
|
+
if self._version.local:
|
341
|
+
return '.'.join(str(x) for x in self._version.local)
|
342
|
+
else:
|
343
|
+
return None
|
344
|
+
|
345
|
+
@property
|
346
|
+
def public(self) -> str:
|
347
|
+
return str(self).split('+', 1)[0]
|
348
|
+
|
349
|
+
@property
|
350
|
+
def base_version(self) -> str:
|
351
|
+
parts = []
|
352
|
+
|
353
|
+
if self.epoch != 0:
|
354
|
+
parts.append(f'{self.epoch}!')
|
355
|
+
|
356
|
+
parts.append('.'.join(str(x) for x in self.release))
|
357
|
+
|
358
|
+
return ''.join(parts)
|
359
|
+
|
360
|
+
@property
|
361
|
+
def is_prerelease(self) -> bool:
|
362
|
+
return self.dev is not None or self.pre is not None
|
363
|
+
|
364
|
+
@property
|
365
|
+
def is_postrelease(self) -> bool:
|
366
|
+
return self.post is not None
|
367
|
+
|
368
|
+
@property
|
369
|
+
def is_devrelease(self) -> bool:
|
370
|
+
return self.dev is not None
|
371
|
+
|
372
|
+
@property
|
373
|
+
def major(self) -> int:
|
374
|
+
return self.release[0] if len(self.release) >= 1 else 0
|
375
|
+
|
376
|
+
@property
|
377
|
+
def minor(self) -> int:
|
378
|
+
return self.release[1] if len(self.release) >= 2 else 0
|
379
|
+
|
380
|
+
@property
|
381
|
+
def micro(self) -> int:
|
382
|
+
return self.release[2] if len(self.release) >= 3 else 0
|
383
|
+
|
384
|
+
|
385
|
+
def _parse_letter_version(
|
386
|
+
letter: ta.Optional[str],
|
387
|
+
number: ta.Union[str, bytes, ta.SupportsInt, None],
|
388
|
+
) -> ta.Optional[ta.Tuple[str, int]]:
|
389
|
+
if letter:
|
390
|
+
if number is None:
|
391
|
+
number = 0
|
392
|
+
|
393
|
+
letter = letter.lower()
|
394
|
+
if letter == 'alpha':
|
395
|
+
letter = 'a'
|
396
|
+
elif letter == 'beta':
|
397
|
+
letter = 'b'
|
398
|
+
elif letter in ['c', 'pre', 'preview']:
|
399
|
+
letter = 'rc'
|
400
|
+
elif letter in ['rev', 'r']:
|
401
|
+
letter = 'post'
|
402
|
+
|
403
|
+
return letter, int(number)
|
404
|
+
if not letter and number:
|
405
|
+
letter = 'post'
|
406
|
+
return letter, int(number)
|
407
|
+
|
408
|
+
return None
|
409
|
+
|
410
|
+
|
411
|
+
_local_version_separators = re.compile(r'[\._-]')
|
412
|
+
|
413
|
+
|
414
|
+
def _parse_local_version(local: ta.Optional[str]) -> ta.Optional[VersionLocalType]:
|
415
|
+
if local is not None:
|
416
|
+
return tuple(
|
417
|
+
part.lower() if not part.isdigit() else int(part)
|
418
|
+
for part in _local_version_separators.split(local)
|
419
|
+
)
|
420
|
+
return None
|
421
|
+
|
422
|
+
|
423
|
+
def _version_cmpkey(
|
424
|
+
epoch: int,
|
425
|
+
release: ta.Tuple[int, ...],
|
426
|
+
pre: ta.Optional[ta.Tuple[str, int]],
|
427
|
+
post: ta.Optional[ta.Tuple[str, int]],
|
428
|
+
dev: ta.Optional[ta.Tuple[str, int]],
|
429
|
+
local: ta.Optional[VersionLocalType],
|
430
|
+
) -> VersionCmpKey:
|
431
|
+
_release = tuple(reversed(list(itertools.dropwhile(lambda x: x == 0, reversed(release)))))
|
432
|
+
|
433
|
+
if pre is None and post is None and dev is not None:
|
434
|
+
_pre: VersionCmpPrePostDevType = NegativeInfinityVersion
|
435
|
+
elif pre is None:
|
436
|
+
_pre = InfinityVersion
|
437
|
+
else:
|
438
|
+
_pre = pre
|
439
|
+
|
440
|
+
if post is None:
|
441
|
+
_post: VersionCmpPrePostDevType = NegativeInfinityVersion
|
442
|
+
else:
|
443
|
+
_post = post
|
444
|
+
|
445
|
+
if dev is None:
|
446
|
+
_dev: VersionCmpPrePostDevType = InfinityVersion
|
447
|
+
else:
|
448
|
+
_dev = dev
|
449
|
+
|
450
|
+
if local is None:
|
451
|
+
_local: VersionCmpLocalType = NegativeInfinityVersion
|
452
|
+
else:
|
453
|
+
_local = tuple((i, '') if isinstance(i, int) else (NegativeInfinityVersion, i) for i in local)
|
454
|
+
|
455
|
+
return epoch, _release, _pre, _post, _dev, _local
|
456
|
+
|
457
|
+
|
458
|
+
##
|
459
|
+
|
460
|
+
|
461
|
+
def canonicalize_version(
|
462
|
+
version: ta.Union[Version, str],
|
463
|
+
*,
|
464
|
+
strip_trailing_zero: bool = True,
|
465
|
+
) -> str:
|
466
|
+
if isinstance(version, str):
|
467
|
+
try:
|
468
|
+
parsed = Version(version)
|
469
|
+
except InvalidVersion:
|
470
|
+
return version
|
471
|
+
else:
|
472
|
+
parsed = version
|
473
|
+
|
474
|
+
parts = []
|
475
|
+
|
476
|
+
if parsed.epoch != 0:
|
477
|
+
parts.append(f'{parsed.epoch}!')
|
478
|
+
|
479
|
+
release_segment = '.'.join(str(x) for x in parsed.release)
|
480
|
+
if strip_trailing_zero:
|
481
|
+
release_segment = re.sub(r'(\.0)+$', '', release_segment)
|
482
|
+
parts.append(release_segment)
|
483
|
+
|
484
|
+
if parsed.pre is not None:
|
485
|
+
parts.append(''.join(str(x) for x in parsed.pre))
|
486
|
+
|
487
|
+
if parsed.post is not None:
|
488
|
+
parts.append(f'.post{parsed.post}')
|
489
|
+
|
490
|
+
if parsed.dev is not None:
|
491
|
+
parts.append(f'.dev{parsed.dev}')
|
492
|
+
|
493
|
+
if parsed.local is not None:
|
494
|
+
parts.append(f'+{parsed.local}')
|
495
|
+
|
496
|
+
return ''.join(parts)
|
497
|
+
|
498
|
+
|
74
499
|
########################################
|
75
500
|
# ../config.py
|
76
501
|
|
@@ -816,7 +1241,6 @@ def pycharm_debug_connect(prd: PycharmRemoteDebug) -> None:
|
|
816
1241
|
def pycharm_debug_preamble(prd: PycharmRemoteDebug) -> str:
|
817
1242
|
import inspect
|
818
1243
|
import textwrap
|
819
|
-
|
820
1244
|
return textwrap.dedent(f"""
|
821
1245
|
{inspect.getsource(pycharm_debug_connect)}
|
822
1246
|
|
@@ -951,143 +1375,665 @@ def format_num_bytes(num_bytes: int) -> str:
|
|
951
1375
|
|
952
1376
|
|
953
1377
|
########################################
|
954
|
-
#
|
955
|
-
|
956
|
-
|
957
|
-
|
958
|
-
|
959
|
-
|
960
|
-
|
961
|
-
|
962
|
-
|
963
|
-
|
964
|
-
|
965
|
-
|
966
|
-
|
967
|
-
|
968
|
-
|
1378
|
+
# ../../../omdev/packaging/specifiers.py
|
1379
|
+
# Copyright (c) Donald Stufft and individual contributors.
|
1380
|
+
# All rights reserved.
|
1381
|
+
#
|
1382
|
+
# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
|
1383
|
+
# following conditions are met:
|
1384
|
+
#
|
1385
|
+
# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the
|
1386
|
+
# following disclaimer.
|
1387
|
+
#
|
1388
|
+
# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
|
1389
|
+
# following disclaimer in the documentation and/or other materials provided with the distribution.
|
1390
|
+
#
|
1391
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
1392
|
+
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
1393
|
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
1394
|
+
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
1395
|
+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
1396
|
+
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
1397
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. This file is dual licensed under the terms of the
|
1398
|
+
# Apache License, Version 2.0, and the BSD License. See the LICENSE file in the root of this repository for complete
|
1399
|
+
# details.
|
1400
|
+
# https://github.com/pypa/packaging/blob/2c885fe91a54559e2382902dce28428ad2887be5/src/packaging/specifiers.py
|
969
1401
|
|
970
1402
|
|
971
1403
|
##
|
972
1404
|
|
973
1405
|
|
974
|
-
|
975
|
-
|
976
|
-
|
977
|
-
|
1406
|
+
def _coerce_version(version: UnparsedVersion) -> Version:
|
1407
|
+
if not isinstance(version, Version):
|
1408
|
+
version = Version(version)
|
1409
|
+
return version
|
978
1410
|
|
979
|
-
traceback: ta.Optional[str] = None
|
980
|
-
|
981
|
-
exc: ta.Optional[ta.Any] = None # Exception
|
982
1411
|
|
983
|
-
|
1412
|
+
class InvalidSpecifier(ValueError): # noqa
|
1413
|
+
pass
|
984
1414
|
|
985
|
-
@classmethod
|
986
|
-
def of(
|
987
|
-
cls,
|
988
|
-
exc: Exception,
|
989
|
-
*,
|
990
|
-
omit_exc_object: bool = False,
|
991
1415
|
|
992
|
-
|
993
|
-
|
994
|
-
|
995
|
-
|
996
|
-
repr=repr(exc),
|
1416
|
+
class BaseSpecifier(metaclass=abc.ABCMeta):
|
1417
|
+
@abc.abstractmethod
|
1418
|
+
def __str__(self) -> str:
|
1419
|
+
raise NotImplementedError
|
997
1420
|
|
998
|
-
|
999
|
-
|
1000
|
-
|
1001
|
-
),
|
1421
|
+
@abc.abstractmethod
|
1422
|
+
def __hash__(self) -> int:
|
1423
|
+
raise NotImplementedError
|
1002
1424
|
|
1003
|
-
|
1425
|
+
@abc.abstractmethod
|
1426
|
+
def __eq__(self, other: object) -> bool:
|
1427
|
+
raise NotImplementedError
|
1004
1428
|
|
1005
|
-
|
1006
|
-
|
1429
|
+
@property
|
1430
|
+
@abc.abstractmethod
|
1431
|
+
def prereleases(self) -> ta.Optional[bool]:
|
1432
|
+
raise NotImplementedError
|
1007
1433
|
|
1434
|
+
@prereleases.setter
|
1435
|
+
def prereleases(self, value: bool) -> None:
|
1436
|
+
raise NotImplementedError
|
1008
1437
|
|
1009
|
-
class CommandOutputOrException(abc.ABC, ta.Generic[CommandOutputT]):
|
1010
|
-
@property
|
1011
1438
|
@abc.abstractmethod
|
1012
|
-
def
|
1439
|
+
def contains(self, item: str, prereleases: ta.Optional[bool] = None) -> bool:
|
1013
1440
|
raise NotImplementedError
|
1014
1441
|
|
1015
|
-
@property
|
1016
1442
|
@abc.abstractmethod
|
1017
|
-
def
|
1443
|
+
def filter(
|
1444
|
+
self,
|
1445
|
+
iterable: ta.Iterable[UnparsedVersionVar],
|
1446
|
+
prereleases: ta.Optional[bool] = None,
|
1447
|
+
) -> ta.Iterator[UnparsedVersionVar]:
|
1018
1448
|
raise NotImplementedError
|
1019
1449
|
|
1020
1450
|
|
1021
|
-
|
1022
|
-
|
1023
|
-
|
1024
|
-
|
1451
|
+
class Specifier(BaseSpecifier):
|
1452
|
+
_operator_regex_str = r"""
|
1453
|
+
(?P<operator>(~=|==|!=|<=|>=|<|>|===))
|
1454
|
+
"""
|
1025
1455
|
|
1456
|
+
_version_regex_str = r"""
|
1457
|
+
(?P<version>
|
1458
|
+
(?:
|
1459
|
+
(?<====)
|
1460
|
+
\s*
|
1461
|
+
[^\s;)]*
|
1462
|
+
)
|
1463
|
+
|
|
1464
|
+
(?:
|
1465
|
+
(?<===|!=)
|
1466
|
+
\s*
|
1467
|
+
v?
|
1468
|
+
(?:[0-9]+!)?
|
1469
|
+
[0-9]+(?:\.[0-9]+)*
|
1470
|
+
(?:
|
1471
|
+
\.\*
|
1472
|
+
|
|
1473
|
+
(?:
|
1474
|
+
[-_\.]?
|
1475
|
+
(alpha|beta|preview|pre|a|b|c|rc)
|
1476
|
+
[-_\.]?
|
1477
|
+
[0-9]*
|
1478
|
+
)?
|
1479
|
+
(?:
|
1480
|
+
(?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)
|
1481
|
+
)?
|
1482
|
+
(?:[-_\.]?dev[-_\.]?[0-9]*)?
|
1483
|
+
(?:\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*)?
|
1484
|
+
)?
|
1485
|
+
)
|
1486
|
+
|
|
1487
|
+
(?:
|
1488
|
+
(?<=~=)
|
1489
|
+
\s*
|
1490
|
+
v?
|
1491
|
+
(?:[0-9]+!)?
|
1492
|
+
[0-9]+(?:\.[0-9]+)+
|
1493
|
+
(?:
|
1494
|
+
[-_\.]?
|
1495
|
+
(alpha|beta|preview|pre|a|b|c|rc)
|
1496
|
+
[-_\.]?
|
1497
|
+
[0-9]*
|
1498
|
+
)?
|
1499
|
+
(?:
|
1500
|
+
(?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)
|
1501
|
+
)?
|
1502
|
+
(?:[-_\.]?dev[-_\.]?[0-9]*)?
|
1503
|
+
)
|
1504
|
+
|
|
1505
|
+
(?:
|
1506
|
+
(?<!==|!=|~=)
|
1507
|
+
\s*
|
1508
|
+
v?
|
1509
|
+
(?:[0-9]+!)?
|
1510
|
+
[0-9]+(?:\.[0-9]+)*
|
1511
|
+
(?:
|
1512
|
+
[-_\.]?
|
1513
|
+
(alpha|beta|preview|pre|a|b|c|rc)
|
1514
|
+
[-_\.]?
|
1515
|
+
[0-9]*
|
1516
|
+
)?
|
1517
|
+
(?:
|
1518
|
+
(?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)
|
1519
|
+
)?
|
1520
|
+
(?:[-_\.]?dev[-_\.]?[0-9]*)?
|
1521
|
+
)
|
1522
|
+
)
|
1523
|
+
"""
|
1026
1524
|
|
1027
|
-
|
1028
|
-
|
1029
|
-
|
1030
|
-
|
1525
|
+
_regex = re.compile(
|
1526
|
+
r'^\s*' + _operator_regex_str + _version_regex_str + r'\s*$',
|
1527
|
+
re.VERBOSE | re.IGNORECASE,
|
1528
|
+
)
|
1031
1529
|
|
1032
|
-
|
1530
|
+
OPERATORS: ta.ClassVar[ta.Mapping[str, str]] = {
|
1531
|
+
'~=': 'compatible',
|
1532
|
+
'==': 'equal',
|
1533
|
+
'!=': 'not_equal',
|
1534
|
+
'<=': 'less_than_equal',
|
1535
|
+
'>=': 'greater_than_equal',
|
1536
|
+
'<': 'less_than',
|
1537
|
+
'>': 'greater_than',
|
1538
|
+
'===': 'arbitrary',
|
1539
|
+
}
|
1540
|
+
|
1541
|
+
def __init__(
|
1033
1542
|
self,
|
1034
|
-
|
1035
|
-
|
1036
|
-
|
1037
|
-
|
1038
|
-
|
1039
|
-
|
1040
|
-
o = self.execute(cmd)
|
1543
|
+
spec: str = '',
|
1544
|
+
prereleases: ta.Optional[bool] = None,
|
1545
|
+
) -> None:
|
1546
|
+
match = self._regex.search(spec)
|
1547
|
+
if not match:
|
1548
|
+
raise InvalidSpecifier(f"Invalid specifier: '{spec}'")
|
1041
1549
|
|
1042
|
-
|
1043
|
-
|
1044
|
-
|
1550
|
+
self._spec: ta.Tuple[str, str] = (
|
1551
|
+
match.group('operator').strip(),
|
1552
|
+
match.group('version').strip(),
|
1553
|
+
)
|
1045
1554
|
|
1046
|
-
|
1047
|
-
e,
|
1048
|
-
omit_exc_object=omit_exc_object,
|
1049
|
-
cmd=cmd,
|
1050
|
-
))
|
1555
|
+
self._prereleases = prereleases
|
1051
1556
|
|
1052
|
-
|
1053
|
-
|
1557
|
+
@property # type: ignore
|
1558
|
+
def prereleases(self) -> bool:
|
1559
|
+
if self._prereleases is not None:
|
1560
|
+
return self._prereleases
|
1054
1561
|
|
1562
|
+
operator, version = self._spec
|
1563
|
+
if operator in ['==', '>=', '<=', '~=', '===']:
|
1564
|
+
if operator == '==' and version.endswith('.*'):
|
1565
|
+
version = version[:-2]
|
1055
1566
|
|
1056
|
-
|
1567
|
+
if Version(version).is_prerelease:
|
1568
|
+
return True
|
1057
1569
|
|
1570
|
+
return False
|
1058
1571
|
|
1059
|
-
@
|
1060
|
-
|
1061
|
-
|
1572
|
+
@prereleases.setter
|
1573
|
+
def prereleases(self, value: bool) -> None:
|
1574
|
+
self._prereleases = value
|
1062
1575
|
|
1063
|
-
|
1576
|
+
@property
|
1577
|
+
def operator(self) -> str:
|
1578
|
+
return self._spec[0]
|
1064
1579
|
|
1065
1580
|
@property
|
1066
|
-
def
|
1067
|
-
|
1068
|
-
|
1069
|
-
|
1581
|
+
def version(self) -> str:
|
1582
|
+
return self._spec[1]
|
1583
|
+
|
1584
|
+
def __repr__(self) -> str:
|
1585
|
+
pre = (
|
1586
|
+
f', prereleases={self.prereleases!r}'
|
1587
|
+
if self._prereleases is not None
|
1588
|
+
else ''
|
1589
|
+
)
|
1070
1590
|
|
1591
|
+
return f'<{self.__class__.__name__}({str(self)!r}{pre})>'
|
1071
1592
|
|
1072
|
-
|
1593
|
+
def __str__(self) -> str:
|
1594
|
+
return '{}{}'.format(*self._spec)
|
1073
1595
|
|
1596
|
+
@property
|
1597
|
+
def _canonical_spec(self) -> ta.Tuple[str, str]:
|
1598
|
+
canonical_version = canonicalize_version(
|
1599
|
+
self._spec[1],
|
1600
|
+
strip_trailing_zero=(self._spec[0] != '~='),
|
1601
|
+
)
|
1602
|
+
return self._spec[0], canonical_version
|
1074
1603
|
|
1075
|
-
|
1604
|
+
def __hash__(self) -> int:
|
1605
|
+
return hash(self._canonical_spec)
|
1076
1606
|
|
1607
|
+
def __eq__(self, other: object) -> bool:
|
1608
|
+
if isinstance(other, str):
|
1609
|
+
try:
|
1610
|
+
other = self.__class__(str(other))
|
1611
|
+
except InvalidSpecifier:
|
1612
|
+
return NotImplemented
|
1613
|
+
elif not isinstance(other, self.__class__):
|
1614
|
+
return NotImplemented
|
1077
1615
|
|
1078
|
-
|
1079
|
-
class CommandExecutorRegistration:
|
1080
|
-
command_cls: ta.Type[Command]
|
1081
|
-
executor_cls: ta.Type[CommandExecutor]
|
1616
|
+
return self._canonical_spec == other._canonical_spec
|
1082
1617
|
|
1618
|
+
def _get_operator(self, op: str) -> CallableVersionOperator:
|
1619
|
+
operator_callable: CallableVersionOperator = getattr(self, f'_compare_{self.OPERATORS[op]}')
|
1620
|
+
return operator_callable
|
1083
1621
|
|
1084
|
-
|
1622
|
+
def _compare_compatible(self, prospective: Version, spec: str) -> bool:
|
1623
|
+
prefix = _version_join(list(itertools.takewhile(_is_not_version_suffix, _version_split(spec)))[:-1])
|
1624
|
+
prefix += '.*'
|
1625
|
+
return self._get_operator('>=')(prospective, spec) and self._get_operator('==')(prospective, prefix)
|
1085
1626
|
|
1627
|
+
def _compare_equal(self, prospective: Version, spec: str) -> bool:
|
1628
|
+
if spec.endswith('.*'):
|
1629
|
+
normalized_prospective = canonicalize_version(prospective.public, strip_trailing_zero=False)
|
1630
|
+
normalized_spec = canonicalize_version(spec[:-2], strip_trailing_zero=False)
|
1631
|
+
split_spec = _version_split(normalized_spec)
|
1086
1632
|
|
1087
|
-
|
1633
|
+
split_prospective = _version_split(normalized_prospective)
|
1634
|
+
padded_prospective, _ = _pad_version(split_prospective, split_spec)
|
1635
|
+
shortened_prospective = padded_prospective[: len(split_spec)]
|
1088
1636
|
|
1637
|
+
return shortened_prospective == split_spec
|
1089
1638
|
|
1090
|
-
|
1639
|
+
else:
|
1640
|
+
spec_version = Version(spec)
|
1641
|
+
if not spec_version.local:
|
1642
|
+
prospective = Version(prospective.public)
|
1643
|
+
return prospective == spec_version
|
1644
|
+
|
1645
|
+
def _compare_not_equal(self, prospective: Version, spec: str) -> bool:
|
1646
|
+
return not self._compare_equal(prospective, spec)
|
1647
|
+
|
1648
|
+
def _compare_less_than_equal(self, prospective: Version, spec: str) -> bool:
|
1649
|
+
return Version(prospective.public) <= Version(spec)
|
1650
|
+
|
1651
|
+
def _compare_greater_than_equal(self, prospective: Version, spec: str) -> bool:
|
1652
|
+
return Version(prospective.public) >= Version(spec)
|
1653
|
+
|
1654
|
+
def _compare_less_than(self, prospective: Version, spec_str: str) -> bool:
|
1655
|
+
spec = Version(spec_str)
|
1656
|
+
|
1657
|
+
if not prospective < spec:
|
1658
|
+
return False
|
1659
|
+
|
1660
|
+
if not spec.is_prerelease and prospective.is_prerelease:
|
1661
|
+
if Version(prospective.base_version) == Version(spec.base_version):
|
1662
|
+
return False
|
1663
|
+
|
1664
|
+
return True
|
1665
|
+
|
1666
|
+
def _compare_greater_than(self, prospective: Version, spec_str: str) -> bool:
|
1667
|
+
spec = Version(spec_str)
|
1668
|
+
|
1669
|
+
if not prospective > spec:
|
1670
|
+
return False
|
1671
|
+
|
1672
|
+
if not spec.is_postrelease and prospective.is_postrelease:
|
1673
|
+
if Version(prospective.base_version) == Version(spec.base_version):
|
1674
|
+
return False
|
1675
|
+
|
1676
|
+
if prospective.local is not None:
|
1677
|
+
if Version(prospective.base_version) == Version(spec.base_version):
|
1678
|
+
return False
|
1679
|
+
|
1680
|
+
return True
|
1681
|
+
|
1682
|
+
def _compare_arbitrary(self, prospective: Version, spec: str) -> bool:
|
1683
|
+
return str(prospective).lower() == str(spec).lower()
|
1684
|
+
|
1685
|
+
def __contains__(self, item: ta.Union[str, Version]) -> bool:
|
1686
|
+
return self.contains(item)
|
1687
|
+
|
1688
|
+
def contains(self, item: UnparsedVersion, prereleases: ta.Optional[bool] = None) -> bool:
|
1689
|
+
if prereleases is None:
|
1690
|
+
prereleases = self.prereleases
|
1691
|
+
|
1692
|
+
normalized_item = _coerce_version(item)
|
1693
|
+
|
1694
|
+
if normalized_item.is_prerelease and not prereleases:
|
1695
|
+
return False
|
1696
|
+
|
1697
|
+
operator_callable: CallableVersionOperator = self._get_operator(self.operator)
|
1698
|
+
return operator_callable(normalized_item, self.version)
|
1699
|
+
|
1700
|
+
def filter(
|
1701
|
+
self,
|
1702
|
+
iterable: ta.Iterable[UnparsedVersionVar],
|
1703
|
+
prereleases: ta.Optional[bool] = None,
|
1704
|
+
) -> ta.Iterator[UnparsedVersionVar]:
|
1705
|
+
yielded = False
|
1706
|
+
found_prereleases = []
|
1707
|
+
|
1708
|
+
kw = {'prereleases': prereleases if prereleases is not None else True}
|
1709
|
+
|
1710
|
+
for version in iterable:
|
1711
|
+
parsed_version = _coerce_version(version)
|
1712
|
+
|
1713
|
+
if self.contains(parsed_version, **kw):
|
1714
|
+
if parsed_version.is_prerelease and not (prereleases or self.prereleases):
|
1715
|
+
found_prereleases.append(version)
|
1716
|
+
else:
|
1717
|
+
yielded = True
|
1718
|
+
yield version
|
1719
|
+
|
1720
|
+
if not yielded and found_prereleases:
|
1721
|
+
for version in found_prereleases:
|
1722
|
+
yield version
|
1723
|
+
|
1724
|
+
|
1725
|
+
_version_prefix_regex = re.compile(r'^([0-9]+)((?:a|b|c|rc)[0-9]+)$')
|
1726
|
+
|
1727
|
+
|
1728
|
+
def _version_split(version: str) -> ta.List[str]:
|
1729
|
+
result: ta.List[str] = []
|
1730
|
+
|
1731
|
+
epoch, _, rest = version.rpartition('!')
|
1732
|
+
result.append(epoch or '0')
|
1733
|
+
|
1734
|
+
for item in rest.split('.'):
|
1735
|
+
match = _version_prefix_regex.search(item)
|
1736
|
+
if match:
|
1737
|
+
result.extend(match.groups())
|
1738
|
+
else:
|
1739
|
+
result.append(item)
|
1740
|
+
return result
|
1741
|
+
|
1742
|
+
|
1743
|
+
def _version_join(components: ta.List[str]) -> str:
|
1744
|
+
epoch, *rest = components
|
1745
|
+
return f"{epoch}!{'.'.join(rest)}"
|
1746
|
+
|
1747
|
+
|
1748
|
+
def _is_not_version_suffix(segment: str) -> bool:
|
1749
|
+
return not any(segment.startswith(prefix) for prefix in ('dev', 'a', 'b', 'rc', 'post'))
|
1750
|
+
|
1751
|
+
|
1752
|
+
def _pad_version(left: ta.List[str], right: ta.List[str]) -> ta.Tuple[ta.List[str], ta.List[str]]:
|
1753
|
+
left_split, right_split = [], []
|
1754
|
+
|
1755
|
+
left_split.append(list(itertools.takewhile(lambda x: x.isdigit(), left)))
|
1756
|
+
right_split.append(list(itertools.takewhile(lambda x: x.isdigit(), right)))
|
1757
|
+
|
1758
|
+
left_split.append(left[len(left_split[0]):])
|
1759
|
+
right_split.append(right[len(right_split[0]):])
|
1760
|
+
|
1761
|
+
left_split.insert(1, ['0'] * max(0, len(right_split[0]) - len(left_split[0])))
|
1762
|
+
right_split.insert(1, ['0'] * max(0, len(left_split[0]) - len(right_split[0])))
|
1763
|
+
|
1764
|
+
return (
|
1765
|
+
list(itertools.chain.from_iterable(left_split)),
|
1766
|
+
list(itertools.chain.from_iterable(right_split)),
|
1767
|
+
)
|
1768
|
+
|
1769
|
+
|
1770
|
+
class SpecifierSet(BaseSpecifier):
|
1771
|
+
def __init__(
|
1772
|
+
self,
|
1773
|
+
specifiers: str = '',
|
1774
|
+
prereleases: ta.Optional[bool] = None,
|
1775
|
+
) -> None:
|
1776
|
+
split_specifiers = [s.strip() for s in specifiers.split(',') if s.strip()]
|
1777
|
+
|
1778
|
+
self._specs = frozenset(map(Specifier, split_specifiers))
|
1779
|
+
self._prereleases = prereleases
|
1780
|
+
|
1781
|
+
@property
|
1782
|
+
def prereleases(self) -> ta.Optional[bool]:
|
1783
|
+
if self._prereleases is not None:
|
1784
|
+
return self._prereleases
|
1785
|
+
|
1786
|
+
if not self._specs:
|
1787
|
+
return None
|
1788
|
+
|
1789
|
+
return any(s.prereleases for s in self._specs)
|
1790
|
+
|
1791
|
+
@prereleases.setter
|
1792
|
+
def prereleases(self, value: bool) -> None:
|
1793
|
+
self._prereleases = value
|
1794
|
+
|
1795
|
+
def __repr__(self) -> str:
|
1796
|
+
pre = (
|
1797
|
+
f', prereleases={self.prereleases!r}'
|
1798
|
+
if self._prereleases is not None
|
1799
|
+
else ''
|
1800
|
+
)
|
1801
|
+
|
1802
|
+
return f'<SpecifierSet({str(self)!r}{pre})>'
|
1803
|
+
|
1804
|
+
def __str__(self) -> str:
|
1805
|
+
return ','.join(sorted(str(s) for s in self._specs))
|
1806
|
+
|
1807
|
+
def __hash__(self) -> int:
|
1808
|
+
return hash(self._specs)
|
1809
|
+
|
1810
|
+
def __and__(self, other: ta.Union['SpecifierSet', str]) -> 'SpecifierSet':
|
1811
|
+
if isinstance(other, str):
|
1812
|
+
other = SpecifierSet(other)
|
1813
|
+
elif not isinstance(other, SpecifierSet):
|
1814
|
+
return NotImplemented # type: ignore
|
1815
|
+
|
1816
|
+
specifier = SpecifierSet()
|
1817
|
+
specifier._specs = frozenset(self._specs | other._specs)
|
1818
|
+
|
1819
|
+
if self._prereleases is None and other._prereleases is not None:
|
1820
|
+
specifier._prereleases = other._prereleases
|
1821
|
+
elif self._prereleases is not None and other._prereleases is None:
|
1822
|
+
specifier._prereleases = self._prereleases
|
1823
|
+
elif self._prereleases == other._prereleases:
|
1824
|
+
specifier._prereleases = self._prereleases
|
1825
|
+
else:
|
1826
|
+
raise ValueError('Cannot combine SpecifierSets with True and False prerelease overrides.')
|
1827
|
+
|
1828
|
+
return specifier
|
1829
|
+
|
1830
|
+
def __eq__(self, other: object) -> bool:
|
1831
|
+
if isinstance(other, (str, Specifier)):
|
1832
|
+
other = SpecifierSet(str(other))
|
1833
|
+
elif not isinstance(other, SpecifierSet):
|
1834
|
+
return NotImplemented
|
1835
|
+
|
1836
|
+
return self._specs == other._specs
|
1837
|
+
|
1838
|
+
def __len__(self) -> int:
|
1839
|
+
return len(self._specs)
|
1840
|
+
|
1841
|
+
def __iter__(self) -> ta.Iterator[Specifier]:
|
1842
|
+
return iter(self._specs)
|
1843
|
+
|
1844
|
+
def __contains__(self, item: UnparsedVersion) -> bool:
|
1845
|
+
return self.contains(item)
|
1846
|
+
|
1847
|
+
def contains(
|
1848
|
+
self,
|
1849
|
+
item: UnparsedVersion,
|
1850
|
+
prereleases: ta.Optional[bool] = None,
|
1851
|
+
installed: ta.Optional[bool] = None,
|
1852
|
+
) -> bool:
|
1853
|
+
if not isinstance(item, Version):
|
1854
|
+
item = Version(item)
|
1855
|
+
|
1856
|
+
if prereleases is None:
|
1857
|
+
prereleases = self.prereleases
|
1858
|
+
|
1859
|
+
if not prereleases and item.is_prerelease:
|
1860
|
+
return False
|
1861
|
+
|
1862
|
+
if installed and item.is_prerelease:
|
1863
|
+
item = Version(item.base_version)
|
1864
|
+
|
1865
|
+
return all(s.contains(item, prereleases=prereleases) for s in self._specs)
|
1866
|
+
|
1867
|
+
def filter(
|
1868
|
+
self,
|
1869
|
+
iterable: ta.Iterable[UnparsedVersionVar],
|
1870
|
+
prereleases: ta.Optional[bool] = None,
|
1871
|
+
) -> ta.Iterator[UnparsedVersionVar]:
|
1872
|
+
if prereleases is None:
|
1873
|
+
prereleases = self.prereleases
|
1874
|
+
|
1875
|
+
if self._specs:
|
1876
|
+
for spec in self._specs:
|
1877
|
+
iterable = spec.filter(iterable, prereleases=bool(prereleases))
|
1878
|
+
return iter(iterable)
|
1879
|
+
|
1880
|
+
else:
|
1881
|
+
filtered: ta.List[UnparsedVersionVar] = []
|
1882
|
+
found_prereleases: ta.List[UnparsedVersionVar] = []
|
1883
|
+
|
1884
|
+
for item in iterable:
|
1885
|
+
parsed_version = _coerce_version(item)
|
1886
|
+
|
1887
|
+
if parsed_version.is_prerelease and not prereleases:
|
1888
|
+
if not filtered:
|
1889
|
+
found_prereleases.append(item)
|
1890
|
+
else:
|
1891
|
+
filtered.append(item)
|
1892
|
+
|
1893
|
+
if not filtered and found_prereleases and prereleases is None:
|
1894
|
+
return iter(found_prereleases)
|
1895
|
+
|
1896
|
+
return iter(filtered)
|
1897
|
+
|
1898
|
+
|
1899
|
+
########################################
|
1900
|
+
# ../commands/base.py
|
1901
|
+
|
1902
|
+
|
1903
|
+
##
|
1904
|
+
|
1905
|
+
|
1906
|
+
@dc.dataclass(frozen=True)
|
1907
|
+
class Command(abc.ABC, ta.Generic[CommandOutputT]):
|
1908
|
+
@dc.dataclass(frozen=True)
|
1909
|
+
class Output(abc.ABC): # noqa
|
1910
|
+
pass
|
1911
|
+
|
1912
|
+
@ta.final
|
1913
|
+
def execute(self, executor: 'CommandExecutor') -> CommandOutputT:
|
1914
|
+
return check_isinstance(executor.execute(self), self.Output) # type: ignore[return-value]
|
1915
|
+
|
1916
|
+
|
1917
|
+
##
|
1918
|
+
|
1919
|
+
|
1920
|
+
@dc.dataclass(frozen=True)
|
1921
|
+
class CommandException:
|
1922
|
+
name: str
|
1923
|
+
repr: str
|
1924
|
+
|
1925
|
+
traceback: ta.Optional[str] = None
|
1926
|
+
|
1927
|
+
exc: ta.Optional[ta.Any] = None # Exception
|
1928
|
+
|
1929
|
+
cmd: ta.Optional[Command] = None
|
1930
|
+
|
1931
|
+
@classmethod
|
1932
|
+
def of(
|
1933
|
+
cls,
|
1934
|
+
exc: Exception,
|
1935
|
+
*,
|
1936
|
+
omit_exc_object: bool = False,
|
1937
|
+
|
1938
|
+
cmd: ta.Optional[Command] = None,
|
1939
|
+
) -> 'CommandException':
|
1940
|
+
return CommandException(
|
1941
|
+
name=type(exc).__qualname__,
|
1942
|
+
repr=repr(exc),
|
1943
|
+
|
1944
|
+
traceback=(
|
1945
|
+
''.join(traceback.format_tb(exc.__traceback__))
|
1946
|
+
if getattr(exc, '__traceback__', None) is not None else None
|
1947
|
+
),
|
1948
|
+
|
1949
|
+
exc=None if omit_exc_object else exc,
|
1950
|
+
|
1951
|
+
cmd=cmd,
|
1952
|
+
)
|
1953
|
+
|
1954
|
+
|
1955
|
+
class CommandOutputOrException(abc.ABC, ta.Generic[CommandOutputT]):
|
1956
|
+
@property
|
1957
|
+
@abc.abstractmethod
|
1958
|
+
def output(self) -> ta.Optional[CommandOutputT]:
|
1959
|
+
raise NotImplementedError
|
1960
|
+
|
1961
|
+
@property
|
1962
|
+
@abc.abstractmethod
|
1963
|
+
def exception(self) -> ta.Optional[CommandException]:
|
1964
|
+
raise NotImplementedError
|
1965
|
+
|
1966
|
+
|
1967
|
+
@dc.dataclass(frozen=True)
|
1968
|
+
class CommandOutputOrExceptionData(CommandOutputOrException):
|
1969
|
+
output: ta.Optional[Command.Output] = None
|
1970
|
+
exception: ta.Optional[CommandException] = None
|
1971
|
+
|
1972
|
+
|
1973
|
+
class CommandExecutor(abc.ABC, ta.Generic[CommandT, CommandOutputT]):
|
1974
|
+
@abc.abstractmethod
|
1975
|
+
def execute(self, cmd: CommandT) -> CommandOutputT:
|
1976
|
+
raise NotImplementedError
|
1977
|
+
|
1978
|
+
def try_execute(
|
1979
|
+
self,
|
1980
|
+
cmd: CommandT,
|
1981
|
+
*,
|
1982
|
+
log: ta.Optional[logging.Logger] = None,
|
1983
|
+
omit_exc_object: bool = False,
|
1984
|
+
) -> CommandOutputOrException[CommandOutputT]:
|
1985
|
+
try:
|
1986
|
+
o = self.execute(cmd)
|
1987
|
+
|
1988
|
+
except Exception as e: # noqa
|
1989
|
+
if log is not None:
|
1990
|
+
log.exception('Exception executing command: %r', type(cmd))
|
1991
|
+
|
1992
|
+
return CommandOutputOrExceptionData(exception=CommandException.of(
|
1993
|
+
e,
|
1994
|
+
omit_exc_object=omit_exc_object,
|
1995
|
+
cmd=cmd,
|
1996
|
+
))
|
1997
|
+
|
1998
|
+
else:
|
1999
|
+
return CommandOutputOrExceptionData(output=o)
|
2000
|
+
|
2001
|
+
|
2002
|
+
##
|
2003
|
+
|
2004
|
+
|
2005
|
+
@dc.dataclass(frozen=True)
|
2006
|
+
class CommandRegistration:
|
2007
|
+
command_cls: ta.Type[Command]
|
2008
|
+
|
2009
|
+
name: ta.Optional[str] = None
|
2010
|
+
|
2011
|
+
@property
|
2012
|
+
def name_or_default(self) -> str:
|
2013
|
+
if not (cls_name := self.command_cls.__name__).endswith('Command'):
|
2014
|
+
raise NameError(cls_name)
|
2015
|
+
return snake_case(cls_name[:-len('Command')])
|
2016
|
+
|
2017
|
+
|
2018
|
+
CommandRegistrations = ta.NewType('CommandRegistrations', ta.Sequence[CommandRegistration])
|
2019
|
+
|
2020
|
+
|
2021
|
+
##
|
2022
|
+
|
2023
|
+
|
2024
|
+
@dc.dataclass(frozen=True)
|
2025
|
+
class CommandExecutorRegistration:
|
2026
|
+
command_cls: ta.Type[Command]
|
2027
|
+
executor_cls: ta.Type[CommandExecutor]
|
2028
|
+
|
2029
|
+
|
2030
|
+
CommandExecutorRegistrations = ta.NewType('CommandExecutorRegistrations', ta.Sequence[CommandExecutorRegistration])
|
2031
|
+
|
2032
|
+
|
2033
|
+
##
|
2034
|
+
|
2035
|
+
|
2036
|
+
CommandNameMap = ta.NewType('CommandNameMap', ta.Mapping[str, ta.Type[Command]])
|
1091
2037
|
|
1092
2038
|
|
1093
2039
|
def build_command_name_map(crs: CommandRegistrations) -> CommandNameMap:
|
@@ -2770,39 +3716,130 @@ def check_runtime_version() -> None:
|
|
2770
3716
|
|
2771
3717
|
|
2772
3718
|
########################################
|
2773
|
-
#
|
3719
|
+
# ../../../omdev/interp/types.py
|
2774
3720
|
|
2775
3721
|
|
2776
|
-
|
2777
|
-
|
2778
|
-
|
2779
|
-
|
2780
|
-
|
3722
|
+
# See https://peps.python.org/pep-3149/
|
3723
|
+
INTERP_OPT_GLYPHS_BY_ATTR: ta.Mapping[str, str] = collections.OrderedDict([
|
3724
|
+
('debug', 'd'),
|
3725
|
+
('threaded', 't'),
|
3726
|
+
])
|
2781
3727
|
|
3728
|
+
INTERP_OPT_ATTRS_BY_GLYPH: ta.Mapping[str, str] = collections.OrderedDict(
|
3729
|
+
(g, a) for a, g in INTERP_OPT_GLYPHS_BY_ATTR.items()
|
3730
|
+
)
|
2782
3731
|
|
2783
|
-
########################################
|
2784
|
-
# ../commands/execution.py
|
2785
3732
|
|
3733
|
+
@dc.dataclass(frozen=True)
|
3734
|
+
class InterpOpts:
|
3735
|
+
threaded: bool = False
|
3736
|
+
debug: bool = False
|
2786
3737
|
|
2787
|
-
|
3738
|
+
def __str__(self) -> str:
|
3739
|
+
return ''.join(g for a, g in INTERP_OPT_GLYPHS_BY_ATTR.items() if getattr(self, a))
|
2788
3740
|
|
3741
|
+
@classmethod
|
3742
|
+
def parse(cls, s: str) -> 'InterpOpts':
|
3743
|
+
return cls(**{INTERP_OPT_ATTRS_BY_GLYPH[g]: True for g in s})
|
2789
3744
|
|
2790
|
-
|
2791
|
-
def
|
2792
|
-
|
2793
|
-
|
2794
|
-
|
2795
|
-
|
2796
|
-
super().__init__()
|
3745
|
+
@classmethod
|
3746
|
+
def parse_suffix(cls, s: str) -> ta.Tuple[str, 'InterpOpts']:
|
3747
|
+
kw = {}
|
3748
|
+
while s and (a := INTERP_OPT_ATTRS_BY_GLYPH.get(s[-1])):
|
3749
|
+
s, kw[a] = s[:-1], True
|
3750
|
+
return s, cls(**kw)
|
2797
3751
|
|
2798
|
-
self._command_executors = command_executors
|
2799
3752
|
|
2800
|
-
|
2801
|
-
|
2802
|
-
|
3753
|
+
@dc.dataclass(frozen=True)
|
3754
|
+
class InterpVersion:
|
3755
|
+
version: Version
|
3756
|
+
opts: InterpOpts
|
2803
3757
|
|
3758
|
+
def __str__(self) -> str:
|
3759
|
+
return str(self.version) + str(self.opts)
|
2804
3760
|
|
2805
|
-
|
3761
|
+
@classmethod
|
3762
|
+
def parse(cls, s: str) -> 'InterpVersion':
|
3763
|
+
s, o = InterpOpts.parse_suffix(s)
|
3764
|
+
v = Version(s)
|
3765
|
+
return cls(
|
3766
|
+
version=v,
|
3767
|
+
opts=o,
|
3768
|
+
)
|
3769
|
+
|
3770
|
+
@classmethod
|
3771
|
+
def try_parse(cls, s: str) -> ta.Optional['InterpVersion']:
|
3772
|
+
try:
|
3773
|
+
return cls.parse(s)
|
3774
|
+
except (KeyError, InvalidVersion):
|
3775
|
+
return None
|
3776
|
+
|
3777
|
+
|
3778
|
+
@dc.dataclass(frozen=True)
|
3779
|
+
class InterpSpecifier:
|
3780
|
+
specifier: Specifier
|
3781
|
+
opts: InterpOpts
|
3782
|
+
|
3783
|
+
def __str__(self) -> str:
|
3784
|
+
return str(self.specifier) + str(self.opts)
|
3785
|
+
|
3786
|
+
@classmethod
|
3787
|
+
def parse(cls, s: str) -> 'InterpSpecifier':
|
3788
|
+
s, o = InterpOpts.parse_suffix(s)
|
3789
|
+
if not any(s.startswith(o) for o in Specifier.OPERATORS):
|
3790
|
+
s = '~=' + s
|
3791
|
+
return cls(
|
3792
|
+
specifier=Specifier(s),
|
3793
|
+
opts=o,
|
3794
|
+
)
|
3795
|
+
|
3796
|
+
def contains(self, iv: InterpVersion) -> bool:
|
3797
|
+
return self.specifier.contains(iv.version) and self.opts == iv.opts
|
3798
|
+
|
3799
|
+
def __contains__(self, iv: InterpVersion) -> bool:
|
3800
|
+
return self.contains(iv)
|
3801
|
+
|
3802
|
+
|
3803
|
+
@dc.dataclass(frozen=True)
|
3804
|
+
class Interp:
|
3805
|
+
exe: str
|
3806
|
+
version: InterpVersion
|
3807
|
+
|
3808
|
+
|
3809
|
+
########################################
|
3810
|
+
# ../bootstrap.py
|
3811
|
+
|
3812
|
+
|
3813
|
+
@dc.dataclass(frozen=True)
|
3814
|
+
class MainBootstrap:
|
3815
|
+
main_config: MainConfig = MainConfig()
|
3816
|
+
|
3817
|
+
remote_config: RemoteConfig = RemoteConfig()
|
3818
|
+
|
3819
|
+
|
3820
|
+
########################################
|
3821
|
+
# ../commands/execution.py
|
3822
|
+
|
3823
|
+
|
3824
|
+
CommandExecutorMap = ta.NewType('CommandExecutorMap', ta.Mapping[ta.Type[Command], CommandExecutor])
|
3825
|
+
|
3826
|
+
|
3827
|
+
class LocalCommandExecutor(CommandExecutor):
|
3828
|
+
def __init__(
|
3829
|
+
self,
|
3830
|
+
*,
|
3831
|
+
command_executors: CommandExecutorMap,
|
3832
|
+
) -> None:
|
3833
|
+
super().__init__()
|
3834
|
+
|
3835
|
+
self._command_executors = command_executors
|
3836
|
+
|
3837
|
+
def execute(self, cmd: Command) -> Command.Output:
|
3838
|
+
ce: CommandExecutor = self._command_executors[type(cmd)]
|
3839
|
+
return ce.execute(cmd)
|
3840
|
+
|
3841
|
+
|
3842
|
+
########################################
|
2806
3843
|
# ../commands/marshal.py
|
2807
3844
|
|
2808
3845
|
|
@@ -2846,6 +3883,8 @@ class DeployCommand(Command['DeployCommand.Output']):
|
|
2846
3883
|
|
2847
3884
|
class DeployCommandExecutor(CommandExecutor[DeployCommand, DeployCommand.Output]):
|
2848
3885
|
def execute(self, cmd: DeployCommand) -> DeployCommand.Output:
|
3886
|
+
log.info('Deploying!')
|
3887
|
+
|
2849
3888
|
return DeployCommand.Output()
|
2850
3889
|
|
2851
3890
|
|
@@ -2879,10 +3918,12 @@ class RemoteChannel:
|
|
2879
3918
|
self._output = output
|
2880
3919
|
self._msh = msh
|
2881
3920
|
|
3921
|
+
self._lock = threading.RLock()
|
3922
|
+
|
2882
3923
|
def set_marshaler(self, msh: ObjMarshalerManager) -> None:
|
2883
3924
|
self._msh = msh
|
2884
3925
|
|
2885
|
-
def
|
3926
|
+
def _send_obj(self, o: ta.Any, ty: ta.Any = None) -> None:
|
2886
3927
|
j = json_dumps_compact(self._msh.marshal_obj(o, ty))
|
2887
3928
|
d = j.encode('utf-8')
|
2888
3929
|
|
@@ -2890,7 +3931,11 @@ class RemoteChannel:
|
|
2890
3931
|
self._output.write(d)
|
2891
3932
|
self._output.flush()
|
2892
3933
|
|
2893
|
-
def
|
3934
|
+
def send_obj(self, o: ta.Any, ty: ta.Any = None) -> None:
|
3935
|
+
with self._lock:
|
3936
|
+
return self._send_obj(o, ty)
|
3937
|
+
|
3938
|
+
def _recv_obj(self, ty: ta.Type[T]) -> ta.Optional[T]:
|
2894
3939
|
d = self._input.read(4)
|
2895
3940
|
if not d:
|
2896
3941
|
return None
|
@@ -2905,6 +3950,10 @@ class RemoteChannel:
|
|
2905
3950
|
j = json.loads(d.decode('utf-8'))
|
2906
3951
|
return self._msh.unmarshal_obj(j, ty)
|
2907
3952
|
|
3953
|
+
def recv_obj(self, ty: ta.Type[T]) -> ta.Optional[T]:
|
3954
|
+
with self._lock:
|
3955
|
+
return self._recv_obj(ty)
|
3956
|
+
|
2908
3957
|
|
2909
3958
|
########################################
|
2910
3959
|
# ../../../omlish/lite/subprocesses.py
|
@@ -3040,6 +4089,102 @@ def subprocess_close(
|
|
3040
4089
|
proc.wait(timeout)
|
3041
4090
|
|
3042
4091
|
|
4092
|
+
########################################
|
4093
|
+
# ../../../omdev/interp/inspect.py
|
4094
|
+
|
4095
|
+
|
4096
|
+
@dc.dataclass(frozen=True)
|
4097
|
+
class InterpInspection:
|
4098
|
+
exe: str
|
4099
|
+
version: Version
|
4100
|
+
|
4101
|
+
version_str: str
|
4102
|
+
config_vars: ta.Mapping[str, str]
|
4103
|
+
prefix: str
|
4104
|
+
base_prefix: str
|
4105
|
+
|
4106
|
+
@property
|
4107
|
+
def opts(self) -> InterpOpts:
|
4108
|
+
return InterpOpts(
|
4109
|
+
threaded=bool(self.config_vars.get('Py_GIL_DISABLED')),
|
4110
|
+
debug=bool(self.config_vars.get('Py_DEBUG')),
|
4111
|
+
)
|
4112
|
+
|
4113
|
+
@property
|
4114
|
+
def iv(self) -> InterpVersion:
|
4115
|
+
return InterpVersion(
|
4116
|
+
version=self.version,
|
4117
|
+
opts=self.opts,
|
4118
|
+
)
|
4119
|
+
|
4120
|
+
@property
|
4121
|
+
def is_venv(self) -> bool:
|
4122
|
+
return self.prefix != self.base_prefix
|
4123
|
+
|
4124
|
+
|
4125
|
+
class InterpInspector:
|
4126
|
+
|
4127
|
+
def __init__(self) -> None:
|
4128
|
+
super().__init__()
|
4129
|
+
|
4130
|
+
self._cache: ta.Dict[str, ta.Optional[InterpInspection]] = {}
|
4131
|
+
|
4132
|
+
_RAW_INSPECTION_CODE = """
|
4133
|
+
__import__('json').dumps(dict(
|
4134
|
+
version_str=__import__('sys').version,
|
4135
|
+
prefix=__import__('sys').prefix,
|
4136
|
+
base_prefix=__import__('sys').base_prefix,
|
4137
|
+
config_vars=__import__('sysconfig').get_config_vars(),
|
4138
|
+
))"""
|
4139
|
+
|
4140
|
+
_INSPECTION_CODE = ''.join(l.strip() for l in _RAW_INSPECTION_CODE.splitlines())
|
4141
|
+
|
4142
|
+
@staticmethod
|
4143
|
+
def _build_inspection(
|
4144
|
+
exe: str,
|
4145
|
+
output: str,
|
4146
|
+
) -> InterpInspection:
|
4147
|
+
dct = json.loads(output)
|
4148
|
+
|
4149
|
+
version = Version(dct['version_str'].split()[0])
|
4150
|
+
|
4151
|
+
return InterpInspection(
|
4152
|
+
exe=exe,
|
4153
|
+
version=version,
|
4154
|
+
**{k: dct[k] for k in (
|
4155
|
+
'version_str',
|
4156
|
+
'prefix',
|
4157
|
+
'base_prefix',
|
4158
|
+
'config_vars',
|
4159
|
+
)},
|
4160
|
+
)
|
4161
|
+
|
4162
|
+
@classmethod
|
4163
|
+
def running(cls) -> 'InterpInspection':
|
4164
|
+
return cls._build_inspection(sys.executable, eval(cls._INSPECTION_CODE)) # noqa
|
4165
|
+
|
4166
|
+
def _inspect(self, exe: str) -> InterpInspection:
|
4167
|
+
output = subprocess_check_output(exe, '-c', f'print({self._INSPECTION_CODE})', quiet=True)
|
4168
|
+
return self._build_inspection(exe, output.decode())
|
4169
|
+
|
4170
|
+
def inspect(self, exe: str) -> ta.Optional[InterpInspection]:
|
4171
|
+
try:
|
4172
|
+
return self._cache[exe]
|
4173
|
+
except KeyError:
|
4174
|
+
ret: ta.Optional[InterpInspection]
|
4175
|
+
try:
|
4176
|
+
ret = self._inspect(exe)
|
4177
|
+
except Exception as e: # noqa
|
4178
|
+
if log.isEnabledFor(logging.DEBUG):
|
4179
|
+
log.exception('Failed to inspect interp: %s', exe)
|
4180
|
+
ret = None
|
4181
|
+
self._cache[exe] = ret
|
4182
|
+
return ret
|
4183
|
+
|
4184
|
+
|
4185
|
+
INTERP_INSPECTOR = InterpInspector()
|
4186
|
+
|
4187
|
+
|
3043
4188
|
########################################
|
3044
4189
|
# ../commands/subprocess.py
|
3045
4190
|
|
@@ -3062,8 +4207,7 @@ class SubprocessCommand(Command['SubprocessCommand.Output']):
|
|
3062
4207
|
timeout: ta.Optional[float] = None
|
3063
4208
|
|
3064
4209
|
def __post_init__(self) -> None:
|
3065
|
-
|
3066
|
-
raise TypeError(self.cmd)
|
4210
|
+
check_not_isinstance(self.cmd, str)
|
3067
4211
|
|
3068
4212
|
@dc.dataclass(frozen=True)
|
3069
4213
|
class Output(Command.Output):
|
@@ -3200,110 +4344,97 @@ class RemoteSpawning:
|
|
3200
4344
|
|
3201
4345
|
|
3202
4346
|
########################################
|
3203
|
-
#
|
4347
|
+
# ../../../omdev/interp/providers.py
|
4348
|
+
"""
|
4349
|
+
TODO:
|
4350
|
+
- backends
|
4351
|
+
- local builds
|
4352
|
+
- deadsnakes?
|
4353
|
+
- uv
|
4354
|
+
- loose versions
|
4355
|
+
"""
|
3204
4356
|
|
3205
4357
|
|
3206
4358
|
##
|
3207
4359
|
|
3208
4360
|
|
3209
|
-
|
3210
|
-
|
3211
|
-
executor_cls: ta.Optional[ta.Type[CommandExecutor]],
|
3212
|
-
) -> InjectorBindings:
|
3213
|
-
lst: ta.List[InjectorBindingOrBindings] = [
|
3214
|
-
inj.bind(CommandRegistration(command_cls), array=True),
|
3215
|
-
]
|
3216
|
-
|
3217
|
-
if executor_cls is not None:
|
3218
|
-
lst.extend([
|
3219
|
-
inj.bind(executor_cls, singleton=True),
|
3220
|
-
inj.bind(CommandExecutorRegistration(command_cls, executor_cls), array=True),
|
3221
|
-
])
|
3222
|
-
|
3223
|
-
return inj.as_bindings(*lst)
|
4361
|
+
class InterpProvider(abc.ABC):
|
4362
|
+
name: ta.ClassVar[str]
|
3224
4363
|
|
4364
|
+
def __init_subclass__(cls, **kwargs: ta.Any) -> None:
|
4365
|
+
super().__init_subclass__(**kwargs)
|
4366
|
+
if abc.ABC not in cls.__bases__ and 'name' not in cls.__dict__:
|
4367
|
+
sfx = 'InterpProvider'
|
4368
|
+
if not cls.__name__.endswith(sfx):
|
4369
|
+
raise NameError(cls)
|
4370
|
+
setattr(cls, 'name', snake_case(cls.__name__[:-len(sfx)]))
|
3225
4371
|
|
3226
|
-
|
4372
|
+
@abc.abstractmethod
|
4373
|
+
def get_installed_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
|
4374
|
+
raise NotImplementedError
|
3227
4375
|
|
4376
|
+
@abc.abstractmethod
|
4377
|
+
def get_installed_version(self, version: InterpVersion) -> Interp:
|
4378
|
+
raise NotImplementedError
|
3228
4379
|
|
3229
|
-
|
3230
|
-
|
3231
|
-
factory: ta.Callable[[], CommandExecutor]
|
4380
|
+
def get_installable_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
|
4381
|
+
return []
|
3232
4382
|
|
3233
|
-
def
|
3234
|
-
|
4383
|
+
def install_version(self, version: InterpVersion) -> Interp:
|
4384
|
+
raise TypeError
|
3235
4385
|
|
3236
4386
|
|
3237
4387
|
##
|
3238
4388
|
|
3239
4389
|
|
3240
|
-
|
3241
|
-
|
3242
|
-
|
3243
|
-
)
|
3244
|
-
|
3245
|
-
|
3246
|
-
|
3247
|
-
|
3248
|
-
|
3249
|
-
|
3250
|
-
|
3251
|
-
|
3252
|
-
|
3253
|
-
|
3254
|
-
|
3255
|
-
|
3256
|
-
def provide_obj_marshaler_installer(cmds: CommandNameMap) -> ObjMarshalerInstaller:
|
3257
|
-
return ObjMarshalerInstaller(functools.partial(install_command_marshaling, cmds))
|
3258
|
-
|
3259
|
-
lst.append(inj.bind(provide_obj_marshaler_installer, array=True))
|
3260
|
-
|
3261
|
-
#
|
3262
|
-
|
3263
|
-
def provide_command_executor_map(
|
3264
|
-
injector: Injector,
|
3265
|
-
crs: CommandExecutorRegistrations,
|
3266
|
-
) -> CommandExecutorMap:
|
3267
|
-
dct: ta.Dict[ta.Type[Command], CommandExecutor] = {}
|
3268
|
-
|
3269
|
-
cr: CommandExecutorRegistration
|
3270
|
-
for cr in crs:
|
3271
|
-
if cr.command_cls in dct:
|
3272
|
-
raise KeyError(cr.command_cls)
|
4390
|
+
class RunningInterpProvider(InterpProvider):
|
4391
|
+
@cached_nullary
|
4392
|
+
def version(self) -> InterpVersion:
|
4393
|
+
return InterpInspector.running().iv
|
4394
|
+
|
4395
|
+
def get_installed_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
|
4396
|
+
return [self.version()]
|
4397
|
+
|
4398
|
+
def get_installed_version(self, version: InterpVersion) -> Interp:
|
4399
|
+
if version != self.version():
|
4400
|
+
raise KeyError(version)
|
4401
|
+
return Interp(
|
4402
|
+
exe=sys.executable,
|
4403
|
+
version=self.version(),
|
4404
|
+
)
|
3273
4405
|
|
3274
|
-
factory = functools.partial(injector.provide, cr.executor_cls)
|
3275
|
-
if main_config.debug:
|
3276
|
-
ce = factory()
|
3277
|
-
else:
|
3278
|
-
ce = _FactoryCommandExecutor(factory)
|
3279
4406
|
|
3280
|
-
|
4407
|
+
########################################
|
4408
|
+
# ../remote/execution.py
|
3281
4409
|
|
3282
|
-
return CommandExecutorMap(dct)
|
3283
4410
|
|
3284
|
-
|
3285
|
-
inj.bind(provide_command_executor_map, singleton=True),
|
4411
|
+
##
|
3286
4412
|
|
3287
|
-
inj.bind(LocalCommandExecutor, singleton=True, eager=main_config.debug),
|
3288
|
-
])
|
3289
4413
|
|
3290
|
-
|
4414
|
+
class _RemoteExecutionLogHandler(logging.Handler):
|
4415
|
+
def __init__(self, fn: ta.Callable[[str], None]) -> None:
|
4416
|
+
super().__init__()
|
4417
|
+
self._fn = fn
|
3291
4418
|
|
3292
|
-
|
3293
|
-
(
|
3294
|
-
|
3295
|
-
lst.append(bind_command(command_cls, executor_cls))
|
4419
|
+
def emit(self, record):
|
4420
|
+
msg = self.format(record)
|
4421
|
+
self._fn(msg)
|
3296
4422
|
|
3297
|
-
#
|
3298
4423
|
|
3299
|
-
|
4424
|
+
@dc.dataclass(frozen=True)
|
4425
|
+
class _RemoteExecutionRequest:
|
4426
|
+
c: Command
|
3300
4427
|
|
3301
4428
|
|
3302
|
-
|
3303
|
-
|
4429
|
+
@dc.dataclass(frozen=True)
|
4430
|
+
class _RemoteExecutionLog:
|
4431
|
+
s: str
|
3304
4432
|
|
3305
4433
|
|
3306
|
-
|
4434
|
+
@dc.dataclass(frozen=True)
|
4435
|
+
class _RemoteExecutionResponse:
|
4436
|
+
r: ta.Optional[CommandOutputOrExceptionData] = None
|
4437
|
+
l: ta.Optional[_RemoteExecutionLog] = None
|
3307
4438
|
|
3308
4439
|
|
3309
4440
|
def _remote_execution_main() -> None:
|
@@ -3323,20 +4454,44 @@ def _remote_execution_main() -> None:
|
|
3323
4454
|
|
3324
4455
|
chan.set_marshaler(injector[ObjMarshalerManager])
|
3325
4456
|
|
4457
|
+
#
|
4458
|
+
|
4459
|
+
log_lock = threading.RLock()
|
4460
|
+
send_logs = False
|
4461
|
+
|
4462
|
+
def log_fn(s: str) -> None:
|
4463
|
+
with log_lock:
|
4464
|
+
if send_logs:
|
4465
|
+
chan.send_obj(_RemoteExecutionResponse(l=_RemoteExecutionLog(s)))
|
4466
|
+
|
4467
|
+
log_handler = _RemoteExecutionLogHandler(log_fn)
|
4468
|
+
logging.root.addHandler(log_handler)
|
4469
|
+
|
4470
|
+
#
|
4471
|
+
|
3326
4472
|
ce = injector[LocalCommandExecutor]
|
3327
4473
|
|
3328
4474
|
while True:
|
3329
|
-
|
3330
|
-
if
|
4475
|
+
req = chan.recv_obj(_RemoteExecutionRequest)
|
4476
|
+
if req is None:
|
3331
4477
|
break
|
3332
4478
|
|
4479
|
+
with log_lock:
|
4480
|
+
send_logs = True
|
4481
|
+
|
3333
4482
|
r = ce.try_execute(
|
3334
|
-
|
4483
|
+
req.c,
|
3335
4484
|
log=log,
|
3336
4485
|
omit_exc_object=True,
|
3337
4486
|
)
|
3338
4487
|
|
3339
|
-
|
4488
|
+
with log_lock:
|
4489
|
+
send_logs = False
|
4490
|
+
|
4491
|
+
chan.send_obj(_RemoteExecutionResponse(r=CommandOutputOrExceptionData(
|
4492
|
+
output=r.output,
|
4493
|
+
exception=r.exception,
|
4494
|
+
)))
|
3340
4495
|
|
3341
4496
|
|
3342
4497
|
##
|
@@ -3354,12 +4509,17 @@ class RemoteCommandExecutor(CommandExecutor):
|
|
3354
4509
|
self._chan = chan
|
3355
4510
|
|
3356
4511
|
def _remote_execute(self, cmd: Command) -> CommandOutputOrException:
|
3357
|
-
self._chan.send_obj(cmd
|
4512
|
+
self._chan.send_obj(_RemoteExecutionRequest(cmd))
|
3358
4513
|
|
3359
|
-
|
3360
|
-
|
4514
|
+
while True:
|
4515
|
+
if (r := self._chan.recv_obj(_RemoteExecutionResponse)) is None:
|
4516
|
+
raise EOFError
|
3361
4517
|
|
3362
|
-
|
4518
|
+
if r.l is not None:
|
4519
|
+
log.info(r.l.s)
|
4520
|
+
|
4521
|
+
if r.r is not None:
|
4522
|
+
return r.r
|
3363
4523
|
|
3364
4524
|
# @ta.override
|
3365
4525
|
def execute(self, cmd: Command) -> Command.Output:
|
@@ -3465,54 +4625,846 @@ class RemoteExecution:
|
|
3465
4625
|
|
3466
4626
|
|
3467
4627
|
########################################
|
3468
|
-
#
|
4628
|
+
# ../../../omdev/interp/pyenv.py
|
4629
|
+
"""
|
4630
|
+
TODO:
|
4631
|
+
- custom tags
|
4632
|
+
- 'aliases'
|
4633
|
+
- https://github.com/pyenv/pyenv/pull/2966
|
4634
|
+
- https://github.com/pyenv/pyenv/issues/218 (lol)
|
4635
|
+
- probably need custom (temp?) definition file
|
4636
|
+
- *or* python-build directly just into the versions dir?
|
4637
|
+
- optionally install / upgrade pyenv itself
|
4638
|
+
- new vers dont need these custom mac opts, only run on old vers
|
4639
|
+
"""
|
3469
4640
|
|
3470
4641
|
|
3471
|
-
|
3472
|
-
) -> InjectorBindings:
|
3473
|
-
lst: ta.List[InjectorBindingOrBindings] = [
|
3474
|
-
bind_command(DeployCommand, DeployCommandExecutor),
|
3475
|
-
]
|
4642
|
+
##
|
3476
4643
|
|
3477
|
-
return inj.as_bindings(*lst)
|
3478
4644
|
|
4645
|
+
class Pyenv:
|
3479
4646
|
|
3480
|
-
|
3481
|
-
|
4647
|
+
def __init__(
|
4648
|
+
self,
|
4649
|
+
*,
|
4650
|
+
root: ta.Optional[str] = None,
|
4651
|
+
) -> None:
|
4652
|
+
if root is not None and not (isinstance(root, str) and root):
|
4653
|
+
raise ValueError(f'pyenv_root: {root!r}')
|
3482
4654
|
|
4655
|
+
super().__init__()
|
3483
4656
|
|
3484
|
-
|
3485
|
-
*,
|
3486
|
-
remote_config: RemoteConfig,
|
3487
|
-
) -> InjectorBindings:
|
3488
|
-
lst: ta.List[InjectorBindingOrBindings] = [
|
3489
|
-
inj.bind(remote_config),
|
4657
|
+
self._root_kw = root
|
3490
4658
|
|
3491
|
-
|
4659
|
+
@cached_nullary
|
4660
|
+
def root(self) -> ta.Optional[str]:
|
4661
|
+
if self._root_kw is not None:
|
4662
|
+
return self._root_kw
|
3492
4663
|
|
3493
|
-
|
3494
|
-
|
4664
|
+
if shutil.which('pyenv'):
|
4665
|
+
return subprocess_check_output_str('pyenv', 'root')
|
3495
4666
|
|
3496
|
-
|
3497
|
-
|
4667
|
+
d = os.path.expanduser('~/.pyenv')
|
4668
|
+
if os.path.isdir(d) and os.path.isfile(os.path.join(d, 'bin', 'pyenv')):
|
4669
|
+
return d
|
3498
4670
|
|
3499
|
-
|
4671
|
+
return None
|
4672
|
+
|
4673
|
+
@cached_nullary
|
4674
|
+
def exe(self) -> str:
|
4675
|
+
return os.path.join(check_not_none(self.root()), 'bin', 'pyenv')
|
4676
|
+
|
4677
|
+
def version_exes(self) -> ta.List[ta.Tuple[str, str]]:
|
4678
|
+
if (root := self.root()) is None:
|
4679
|
+
return []
|
4680
|
+
ret = []
|
4681
|
+
vp = os.path.join(root, 'versions')
|
4682
|
+
if os.path.isdir(vp):
|
4683
|
+
for dn in os.listdir(vp):
|
4684
|
+
ep = os.path.join(vp, dn, 'bin', 'python')
|
4685
|
+
if not os.path.isfile(ep):
|
4686
|
+
continue
|
4687
|
+
ret.append((dn, ep))
|
4688
|
+
return ret
|
3500
4689
|
|
4690
|
+
def installable_versions(self) -> ta.List[str]:
|
4691
|
+
if self.root() is None:
|
4692
|
+
return []
|
4693
|
+
ret = []
|
4694
|
+
s = subprocess_check_output_str(self.exe(), 'install', '--list')
|
4695
|
+
for l in s.splitlines():
|
4696
|
+
if not l.startswith(' '):
|
4697
|
+
continue
|
4698
|
+
l = l.strip()
|
4699
|
+
if not l:
|
4700
|
+
continue
|
4701
|
+
ret.append(l)
|
4702
|
+
return ret
|
3501
4703
|
|
3502
|
-
|
3503
|
-
|
4704
|
+
def update(self) -> bool:
|
4705
|
+
if (root := self.root()) is None:
|
4706
|
+
return False
|
4707
|
+
if not os.path.isdir(os.path.join(root, '.git')):
|
4708
|
+
return False
|
4709
|
+
subprocess_check_call('git', 'pull', cwd=root)
|
4710
|
+
return True
|
3504
4711
|
|
3505
4712
|
|
3506
4713
|
##
|
3507
4714
|
|
3508
4715
|
|
3509
|
-
|
3510
|
-
|
3511
|
-
|
3512
|
-
|
3513
|
-
|
3514
|
-
|
3515
|
-
|
4716
|
+
@dc.dataclass(frozen=True)
|
4717
|
+
class PyenvInstallOpts:
|
4718
|
+
opts: ta.Sequence[str] = ()
|
4719
|
+
conf_opts: ta.Sequence[str] = ()
|
4720
|
+
cflags: ta.Sequence[str] = ()
|
4721
|
+
ldflags: ta.Sequence[str] = ()
|
4722
|
+
env: ta.Mapping[str, str] = dc.field(default_factory=dict)
|
4723
|
+
|
4724
|
+
def merge(self, *others: 'PyenvInstallOpts') -> 'PyenvInstallOpts':
|
4725
|
+
return PyenvInstallOpts(
|
4726
|
+
opts=list(itertools.chain.from_iterable(o.opts for o in [self, *others])),
|
4727
|
+
conf_opts=list(itertools.chain.from_iterable(o.conf_opts for o in [self, *others])),
|
4728
|
+
cflags=list(itertools.chain.from_iterable(o.cflags for o in [self, *others])),
|
4729
|
+
ldflags=list(itertools.chain.from_iterable(o.ldflags for o in [self, *others])),
|
4730
|
+
env=dict(itertools.chain.from_iterable(o.env.items() for o in [self, *others])),
|
4731
|
+
)
|
4732
|
+
|
4733
|
+
|
4734
|
+
# TODO: https://github.com/pyenv/pyenv/blob/master/plugins/python-build/README.md#building-for-maximum-performance
|
4735
|
+
DEFAULT_PYENV_INSTALL_OPTS = PyenvInstallOpts(
|
4736
|
+
opts=[
|
4737
|
+
'-s',
|
4738
|
+
'-v',
|
4739
|
+
'-k',
|
4740
|
+
],
|
4741
|
+
conf_opts=[
|
4742
|
+
# FIXME: breaks on mac for older py's
|
4743
|
+
'--enable-loadable-sqlite-extensions',
|
4744
|
+
|
4745
|
+
# '--enable-shared',
|
4746
|
+
|
4747
|
+
'--enable-optimizations',
|
4748
|
+
'--with-lto',
|
4749
|
+
|
4750
|
+
# '--enable-profiling', # ?
|
4751
|
+
|
4752
|
+
# '--enable-ipv6', # ?
|
4753
|
+
],
|
4754
|
+
cflags=[
|
4755
|
+
# '-march=native',
|
4756
|
+
# '-mtune=native',
|
4757
|
+
],
|
4758
|
+
)
|
4759
|
+
|
4760
|
+
DEBUG_PYENV_INSTALL_OPTS = PyenvInstallOpts(opts=['-g'])
|
4761
|
+
|
4762
|
+
THREADED_PYENV_INSTALL_OPTS = PyenvInstallOpts(conf_opts=['--disable-gil'])
|
4763
|
+
|
4764
|
+
|
4765
|
+
#
|
4766
|
+
|
4767
|
+
|
4768
|
+
class PyenvInstallOptsProvider(abc.ABC):
|
4769
|
+
@abc.abstractmethod
|
4770
|
+
def opts(self) -> PyenvInstallOpts:
|
4771
|
+
raise NotImplementedError
|
4772
|
+
|
4773
|
+
|
4774
|
+
class LinuxPyenvInstallOpts(PyenvInstallOptsProvider):
|
4775
|
+
def opts(self) -> PyenvInstallOpts:
|
4776
|
+
return PyenvInstallOpts()
|
4777
|
+
|
4778
|
+
|
4779
|
+
class DarwinPyenvInstallOpts(PyenvInstallOptsProvider):
|
4780
|
+
|
4781
|
+
@cached_nullary
|
4782
|
+
def framework_opts(self) -> PyenvInstallOpts:
|
4783
|
+
return PyenvInstallOpts(conf_opts=['--enable-framework'])
|
4784
|
+
|
4785
|
+
@cached_nullary
|
4786
|
+
def has_brew(self) -> bool:
|
4787
|
+
return shutil.which('brew') is not None
|
4788
|
+
|
4789
|
+
BREW_DEPS: ta.Sequence[str] = [
|
4790
|
+
'openssl',
|
4791
|
+
'readline',
|
4792
|
+
'sqlite3',
|
4793
|
+
'zlib',
|
4794
|
+
]
|
4795
|
+
|
4796
|
+
@cached_nullary
|
4797
|
+
def brew_deps_opts(self) -> PyenvInstallOpts:
|
4798
|
+
cflags = []
|
4799
|
+
ldflags = []
|
4800
|
+
for dep in self.BREW_DEPS:
|
4801
|
+
dep_prefix = subprocess_check_output_str('brew', '--prefix', dep)
|
4802
|
+
cflags.append(f'-I{dep_prefix}/include')
|
4803
|
+
ldflags.append(f'-L{dep_prefix}/lib')
|
4804
|
+
return PyenvInstallOpts(
|
4805
|
+
cflags=cflags,
|
4806
|
+
ldflags=ldflags,
|
4807
|
+
)
|
4808
|
+
|
4809
|
+
@cached_nullary
|
4810
|
+
def brew_tcl_opts(self) -> PyenvInstallOpts:
|
4811
|
+
if subprocess_try_output('brew', '--prefix', 'tcl-tk') is None:
|
4812
|
+
return PyenvInstallOpts()
|
4813
|
+
|
4814
|
+
tcl_tk_prefix = subprocess_check_output_str('brew', '--prefix', 'tcl-tk')
|
4815
|
+
tcl_tk_ver_str = subprocess_check_output_str('brew', 'ls', '--versions', 'tcl-tk')
|
4816
|
+
tcl_tk_ver = '.'.join(tcl_tk_ver_str.split()[1].split('.')[:2])
|
4817
|
+
|
4818
|
+
return PyenvInstallOpts(conf_opts=[
|
4819
|
+
f"--with-tcltk-includes='-I{tcl_tk_prefix}/include'",
|
4820
|
+
f"--with-tcltk-libs='-L{tcl_tk_prefix}/lib -ltcl{tcl_tk_ver} -ltk{tcl_tk_ver}'",
|
4821
|
+
])
|
4822
|
+
|
4823
|
+
# @cached_nullary
|
4824
|
+
# def brew_ssl_opts(self) -> PyenvInstallOpts:
|
4825
|
+
# pkg_config_path = subprocess_check_output_str('brew', '--prefix', 'openssl')
|
4826
|
+
# if 'PKG_CONFIG_PATH' in os.environ:
|
4827
|
+
# pkg_config_path += ':' + os.environ['PKG_CONFIG_PATH']
|
4828
|
+
# return PyenvInstallOpts(env={'PKG_CONFIG_PATH': pkg_config_path})
|
4829
|
+
|
4830
|
+
def opts(self) -> PyenvInstallOpts:
|
4831
|
+
return PyenvInstallOpts().merge(
|
4832
|
+
self.framework_opts(),
|
4833
|
+
self.brew_deps_opts(),
|
4834
|
+
self.brew_tcl_opts(),
|
4835
|
+
# self.brew_ssl_opts(),
|
4836
|
+
)
|
4837
|
+
|
4838
|
+
|
4839
|
+
PLATFORM_PYENV_INSTALL_OPTS: ta.Dict[str, PyenvInstallOptsProvider] = {
|
4840
|
+
'darwin': DarwinPyenvInstallOpts(),
|
4841
|
+
'linux': LinuxPyenvInstallOpts(),
|
4842
|
+
}
|
4843
|
+
|
4844
|
+
|
4845
|
+
##
|
4846
|
+
|
4847
|
+
|
4848
|
+
class PyenvVersionInstaller:
|
4849
|
+
"""
|
4850
|
+
Messy: can install freethreaded build with a 't' suffixed version str _or_ by THREADED_PYENV_INSTALL_OPTS - need
|
4851
|
+
latter to build custom interp with ft, need former to use canned / blessed interps. Muh.
|
4852
|
+
"""
|
4853
|
+
|
4854
|
+
def __init__(
|
4855
|
+
self,
|
4856
|
+
version: str,
|
4857
|
+
opts: ta.Optional[PyenvInstallOpts] = None,
|
4858
|
+
interp_opts: InterpOpts = InterpOpts(),
|
4859
|
+
*,
|
4860
|
+
install_name: ta.Optional[str] = None,
|
4861
|
+
no_default_opts: bool = False,
|
4862
|
+
pyenv: Pyenv = Pyenv(),
|
4863
|
+
) -> None:
|
4864
|
+
super().__init__()
|
4865
|
+
|
4866
|
+
if no_default_opts:
|
4867
|
+
if opts is None:
|
4868
|
+
opts = PyenvInstallOpts()
|
4869
|
+
else:
|
4870
|
+
lst = [opts if opts is not None else DEFAULT_PYENV_INSTALL_OPTS]
|
4871
|
+
if interp_opts.debug:
|
4872
|
+
lst.append(DEBUG_PYENV_INSTALL_OPTS)
|
4873
|
+
if interp_opts.threaded:
|
4874
|
+
lst.append(THREADED_PYENV_INSTALL_OPTS)
|
4875
|
+
lst.append(PLATFORM_PYENV_INSTALL_OPTS[sys.platform].opts())
|
4876
|
+
opts = PyenvInstallOpts().merge(*lst)
|
4877
|
+
|
4878
|
+
self._version = version
|
4879
|
+
self._opts = opts
|
4880
|
+
self._interp_opts = interp_opts
|
4881
|
+
self._given_install_name = install_name
|
4882
|
+
|
4883
|
+
self._no_default_opts = no_default_opts
|
4884
|
+
self._pyenv = pyenv
|
4885
|
+
|
4886
|
+
@property
|
4887
|
+
def version(self) -> str:
|
4888
|
+
return self._version
|
4889
|
+
|
4890
|
+
@property
|
4891
|
+
def opts(self) -> PyenvInstallOpts:
|
4892
|
+
return self._opts
|
4893
|
+
|
4894
|
+
@cached_nullary
|
4895
|
+
def install_name(self) -> str:
|
4896
|
+
if self._given_install_name is not None:
|
4897
|
+
return self._given_install_name
|
4898
|
+
return self._version + ('-debug' if self._interp_opts.debug else '')
|
4899
|
+
|
4900
|
+
@cached_nullary
|
4901
|
+
def install_dir(self) -> str:
|
4902
|
+
return str(os.path.join(check_not_none(self._pyenv.root()), 'versions', self.install_name()))
|
4903
|
+
|
4904
|
+
@cached_nullary
|
4905
|
+
def install(self) -> str:
|
4906
|
+
env = {**os.environ, **self._opts.env}
|
4907
|
+
for k, l in [
|
4908
|
+
('CFLAGS', self._opts.cflags),
|
4909
|
+
('LDFLAGS', self._opts.ldflags),
|
4910
|
+
('PYTHON_CONFIGURE_OPTS', self._opts.conf_opts),
|
4911
|
+
]:
|
4912
|
+
v = ' '.join(l)
|
4913
|
+
if k in os.environ:
|
4914
|
+
v += ' ' + os.environ[k]
|
4915
|
+
env[k] = v
|
4916
|
+
|
4917
|
+
conf_args = [
|
4918
|
+
*self._opts.opts,
|
4919
|
+
self._version,
|
4920
|
+
]
|
4921
|
+
|
4922
|
+
if self._given_install_name is not None:
|
4923
|
+
full_args = [
|
4924
|
+
os.path.join(check_not_none(self._pyenv.root()), 'plugins', 'python-build', 'bin', 'python-build'),
|
4925
|
+
*conf_args,
|
4926
|
+
self.install_dir(),
|
4927
|
+
]
|
4928
|
+
else:
|
4929
|
+
full_args = [
|
4930
|
+
self._pyenv.exe(),
|
4931
|
+
'install',
|
4932
|
+
*conf_args,
|
4933
|
+
]
|
4934
|
+
|
4935
|
+
subprocess_check_call(
|
4936
|
+
*full_args,
|
4937
|
+
env=env,
|
4938
|
+
)
|
4939
|
+
|
4940
|
+
exe = os.path.join(self.install_dir(), 'bin', 'python')
|
4941
|
+
if not os.path.isfile(exe):
|
4942
|
+
raise RuntimeError(f'Interpreter not found: {exe}')
|
4943
|
+
return exe
|
4944
|
+
|
4945
|
+
|
4946
|
+
##
|
4947
|
+
|
4948
|
+
|
4949
|
+
class PyenvInterpProvider(InterpProvider):
|
4950
|
+
|
4951
|
+
def __init__(
|
4952
|
+
self,
|
4953
|
+
pyenv: Pyenv = Pyenv(),
|
4954
|
+
|
4955
|
+
inspect: bool = False,
|
4956
|
+
inspector: InterpInspector = INTERP_INSPECTOR,
|
4957
|
+
|
4958
|
+
*,
|
4959
|
+
|
4960
|
+
try_update: bool = False,
|
4961
|
+
) -> None:
|
4962
|
+
super().__init__()
|
4963
|
+
|
4964
|
+
self._pyenv = pyenv
|
4965
|
+
|
4966
|
+
self._inspect = inspect
|
4967
|
+
self._inspector = inspector
|
4968
|
+
|
4969
|
+
self._try_update = try_update
|
4970
|
+
|
4971
|
+
#
|
4972
|
+
|
4973
|
+
@staticmethod
|
4974
|
+
def guess_version(s: str) -> ta.Optional[InterpVersion]:
|
4975
|
+
def strip_sfx(s: str, sfx: str) -> ta.Tuple[str, bool]:
|
4976
|
+
if s.endswith(sfx):
|
4977
|
+
return s[:-len(sfx)], True
|
4978
|
+
return s, False
|
4979
|
+
ok = {}
|
4980
|
+
s, ok['debug'] = strip_sfx(s, '-debug')
|
4981
|
+
s, ok['threaded'] = strip_sfx(s, 't')
|
4982
|
+
try:
|
4983
|
+
v = Version(s)
|
4984
|
+
except InvalidVersion:
|
4985
|
+
return None
|
4986
|
+
return InterpVersion(v, InterpOpts(**ok))
|
4987
|
+
|
4988
|
+
class Installed(ta.NamedTuple):
|
4989
|
+
name: str
|
4990
|
+
exe: str
|
4991
|
+
version: InterpVersion
|
4992
|
+
|
4993
|
+
def _make_installed(self, vn: str, ep: str) -> ta.Optional[Installed]:
|
4994
|
+
iv: ta.Optional[InterpVersion]
|
4995
|
+
if self._inspect:
|
4996
|
+
try:
|
4997
|
+
iv = check_not_none(self._inspector.inspect(ep)).iv
|
4998
|
+
except Exception as e: # noqa
|
4999
|
+
return None
|
5000
|
+
else:
|
5001
|
+
iv = self.guess_version(vn)
|
5002
|
+
if iv is None:
|
5003
|
+
return None
|
5004
|
+
return PyenvInterpProvider.Installed(
|
5005
|
+
name=vn,
|
5006
|
+
exe=ep,
|
5007
|
+
version=iv,
|
5008
|
+
)
|
5009
|
+
|
5010
|
+
def installed(self) -> ta.Sequence[Installed]:
|
5011
|
+
ret: ta.List[PyenvInterpProvider.Installed] = []
|
5012
|
+
for vn, ep in self._pyenv.version_exes():
|
5013
|
+
if (i := self._make_installed(vn, ep)) is None:
|
5014
|
+
log.debug('Invalid pyenv version: %s', vn)
|
5015
|
+
continue
|
5016
|
+
ret.append(i)
|
5017
|
+
return ret
|
5018
|
+
|
5019
|
+
#
|
5020
|
+
|
5021
|
+
def get_installed_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
|
5022
|
+
return [i.version for i in self.installed()]
|
5023
|
+
|
5024
|
+
def get_installed_version(self, version: InterpVersion) -> Interp:
|
5025
|
+
for i in self.installed():
|
5026
|
+
if i.version == version:
|
5027
|
+
return Interp(
|
5028
|
+
exe=i.exe,
|
5029
|
+
version=i.version,
|
5030
|
+
)
|
5031
|
+
raise KeyError(version)
|
5032
|
+
|
5033
|
+
#
|
5034
|
+
|
5035
|
+
def _get_installable_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
|
5036
|
+
lst = []
|
5037
|
+
|
5038
|
+
for vs in self._pyenv.installable_versions():
|
5039
|
+
if (iv := self.guess_version(vs)) is None:
|
5040
|
+
continue
|
5041
|
+
if iv.opts.debug:
|
5042
|
+
raise Exception('Pyenv installable versions not expected to have debug suffix')
|
5043
|
+
for d in [False, True]:
|
5044
|
+
lst.append(dc.replace(iv, opts=dc.replace(iv.opts, debug=d)))
|
5045
|
+
|
5046
|
+
return lst
|
5047
|
+
|
5048
|
+
def get_installable_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
|
5049
|
+
lst = self._get_installable_versions(spec)
|
5050
|
+
|
5051
|
+
if self._try_update and not any(v in spec for v in lst):
|
5052
|
+
if self._pyenv.update():
|
5053
|
+
lst = self._get_installable_versions(spec)
|
5054
|
+
|
5055
|
+
return lst
|
5056
|
+
|
5057
|
+
def install_version(self, version: InterpVersion) -> Interp:
|
5058
|
+
inst_version = str(version.version)
|
5059
|
+
inst_opts = version.opts
|
5060
|
+
if inst_opts.threaded:
|
5061
|
+
inst_version += 't'
|
5062
|
+
inst_opts = dc.replace(inst_opts, threaded=False)
|
5063
|
+
|
5064
|
+
installer = PyenvVersionInstaller(
|
5065
|
+
inst_version,
|
5066
|
+
interp_opts=inst_opts,
|
5067
|
+
)
|
5068
|
+
|
5069
|
+
exe = installer.install()
|
5070
|
+
return Interp(exe, version)
|
5071
|
+
|
5072
|
+
|
5073
|
+
########################################
|
5074
|
+
# ../../../omdev/interp/system.py
|
5075
|
+
"""
|
5076
|
+
TODO:
|
5077
|
+
- python, python3, python3.12, ...
|
5078
|
+
- check if path py's are venvs: sys.prefix != sys.base_prefix
|
5079
|
+
"""
|
5080
|
+
|
5081
|
+
|
5082
|
+
##
|
5083
|
+
|
5084
|
+
|
5085
|
+
@dc.dataclass(frozen=True)
|
5086
|
+
class SystemInterpProvider(InterpProvider):
|
5087
|
+
cmd: str = 'python3'
|
5088
|
+
path: ta.Optional[str] = None
|
5089
|
+
|
5090
|
+
inspect: bool = False
|
5091
|
+
inspector: InterpInspector = INTERP_INSPECTOR
|
5092
|
+
|
5093
|
+
#
|
5094
|
+
|
5095
|
+
@staticmethod
|
5096
|
+
def _re_which(
|
5097
|
+
pat: re.Pattern,
|
5098
|
+
*,
|
5099
|
+
mode: int = os.F_OK | os.X_OK,
|
5100
|
+
path: ta.Optional[str] = None,
|
5101
|
+
) -> ta.List[str]:
|
5102
|
+
if path is None:
|
5103
|
+
path = os.environ.get('PATH', None)
|
5104
|
+
if path is None:
|
5105
|
+
try:
|
5106
|
+
path = os.confstr('CS_PATH')
|
5107
|
+
except (AttributeError, ValueError):
|
5108
|
+
path = os.defpath
|
5109
|
+
|
5110
|
+
if not path:
|
5111
|
+
return []
|
5112
|
+
|
5113
|
+
path = os.fsdecode(path)
|
5114
|
+
pathlst = path.split(os.pathsep)
|
5115
|
+
|
5116
|
+
def _access_check(fn: str, mode: int) -> bool:
|
5117
|
+
return os.path.exists(fn) and os.access(fn, mode)
|
5118
|
+
|
5119
|
+
out = []
|
5120
|
+
seen = set()
|
5121
|
+
for d in pathlst:
|
5122
|
+
normdir = os.path.normcase(d)
|
5123
|
+
if normdir not in seen:
|
5124
|
+
seen.add(normdir)
|
5125
|
+
if not _access_check(normdir, mode):
|
5126
|
+
continue
|
5127
|
+
for thefile in os.listdir(d):
|
5128
|
+
name = os.path.join(d, thefile)
|
5129
|
+
if not (
|
5130
|
+
os.path.isfile(name) and
|
5131
|
+
pat.fullmatch(thefile) and
|
5132
|
+
_access_check(name, mode)
|
5133
|
+
):
|
5134
|
+
continue
|
5135
|
+
out.append(name)
|
5136
|
+
|
5137
|
+
return out
|
5138
|
+
|
5139
|
+
@cached_nullary
|
5140
|
+
def exes(self) -> ta.List[str]:
|
5141
|
+
return self._re_which(
|
5142
|
+
re.compile(r'python3(\.\d+)?'),
|
5143
|
+
path=self.path,
|
5144
|
+
)
|
5145
|
+
|
5146
|
+
#
|
5147
|
+
|
5148
|
+
def get_exe_version(self, exe: str) -> ta.Optional[InterpVersion]:
|
5149
|
+
if not self.inspect:
|
5150
|
+
s = os.path.basename(exe)
|
5151
|
+
if s.startswith('python'):
|
5152
|
+
s = s[len('python'):]
|
5153
|
+
if '.' in s:
|
5154
|
+
try:
|
5155
|
+
return InterpVersion.parse(s)
|
5156
|
+
except InvalidVersion:
|
5157
|
+
pass
|
5158
|
+
ii = self.inspector.inspect(exe)
|
5159
|
+
return ii.iv if ii is not None else None
|
5160
|
+
|
5161
|
+
def exe_versions(self) -> ta.Sequence[ta.Tuple[str, InterpVersion]]:
|
5162
|
+
lst = []
|
5163
|
+
for e in self.exes():
|
5164
|
+
if (ev := self.get_exe_version(e)) is None:
|
5165
|
+
log.debug('Invalid system version: %s', e)
|
5166
|
+
continue
|
5167
|
+
lst.append((e, ev))
|
5168
|
+
return lst
|
5169
|
+
|
5170
|
+
#
|
5171
|
+
|
5172
|
+
def get_installed_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
|
5173
|
+
return [ev for e, ev in self.exe_versions()]
|
5174
|
+
|
5175
|
+
def get_installed_version(self, version: InterpVersion) -> Interp:
|
5176
|
+
for e, ev in self.exe_versions():
|
5177
|
+
if ev != version:
|
5178
|
+
continue
|
5179
|
+
return Interp(
|
5180
|
+
exe=e,
|
5181
|
+
version=ev,
|
5182
|
+
)
|
5183
|
+
raise KeyError(version)
|
5184
|
+
|
5185
|
+
|
5186
|
+
########################################
|
5187
|
+
# ../remote/inject.py
|
5188
|
+
|
5189
|
+
|
5190
|
+
def bind_remote(
|
5191
|
+
*,
|
5192
|
+
remote_config: RemoteConfig,
|
5193
|
+
) -> InjectorBindings:
|
5194
|
+
lst: ta.List[InjectorBindingOrBindings] = [
|
5195
|
+
inj.bind(remote_config),
|
5196
|
+
|
5197
|
+
inj.bind(RemoteSpawning, singleton=True),
|
5198
|
+
|
5199
|
+
inj.bind(RemoteExecution, singleton=True),
|
5200
|
+
]
|
5201
|
+
|
5202
|
+
if (pf := remote_config.payload_file) is not None:
|
5203
|
+
lst.append(inj.bind(pf, to_key=RemoteExecutionPayloadFile))
|
5204
|
+
|
5205
|
+
return inj.as_bindings(*lst)
|
5206
|
+
|
5207
|
+
|
5208
|
+
########################################
|
5209
|
+
# ../../../omdev/interp/resolvers.py
|
5210
|
+
|
5211
|
+
|
5212
|
+
INTERP_PROVIDER_TYPES_BY_NAME: ta.Mapping[str, ta.Type[InterpProvider]] = {
|
5213
|
+
cls.name: cls for cls in deep_subclasses(InterpProvider) if abc.ABC not in cls.__bases__ # type: ignore
|
5214
|
+
}
|
5215
|
+
|
5216
|
+
|
5217
|
+
class InterpResolver:
|
5218
|
+
def __init__(
|
5219
|
+
self,
|
5220
|
+
providers: ta.Sequence[ta.Tuple[str, InterpProvider]],
|
5221
|
+
) -> None:
|
5222
|
+
super().__init__()
|
5223
|
+
self._providers: ta.Mapping[str, InterpProvider] = collections.OrderedDict(providers)
|
5224
|
+
|
5225
|
+
def _resolve_installed(self, spec: InterpSpecifier) -> ta.Optional[ta.Tuple[InterpProvider, InterpVersion]]:
|
5226
|
+
lst = [
|
5227
|
+
(i, si)
|
5228
|
+
for i, p in enumerate(self._providers.values())
|
5229
|
+
for si in p.get_installed_versions(spec)
|
5230
|
+
if spec.contains(si)
|
5231
|
+
]
|
5232
|
+
|
5233
|
+
slst = sorted(lst, key=lambda t: (-t[0], t[1].version))
|
5234
|
+
if not slst:
|
5235
|
+
return None
|
5236
|
+
|
5237
|
+
bi, bv = slst[-1]
|
5238
|
+
bp = list(self._providers.values())[bi]
|
5239
|
+
return (bp, bv)
|
5240
|
+
|
5241
|
+
def resolve(
|
5242
|
+
self,
|
5243
|
+
spec: InterpSpecifier,
|
5244
|
+
*,
|
5245
|
+
install: bool = False,
|
5246
|
+
) -> ta.Optional[Interp]:
|
5247
|
+
tup = self._resolve_installed(spec)
|
5248
|
+
if tup is not None:
|
5249
|
+
bp, bv = tup
|
5250
|
+
return bp.get_installed_version(bv)
|
5251
|
+
|
5252
|
+
if not install:
|
5253
|
+
return None
|
5254
|
+
|
5255
|
+
tp = list(self._providers.values())[0] # noqa
|
5256
|
+
|
5257
|
+
sv = sorted(
|
5258
|
+
[s for s in tp.get_installable_versions(spec) if s in spec],
|
5259
|
+
key=lambda s: s.version,
|
5260
|
+
)
|
5261
|
+
if not sv:
|
5262
|
+
return None
|
5263
|
+
|
5264
|
+
bv = sv[-1]
|
5265
|
+
return tp.install_version(bv)
|
5266
|
+
|
5267
|
+
def list(self, spec: InterpSpecifier) -> None:
|
5268
|
+
print('installed:')
|
5269
|
+
for n, p in self._providers.items():
|
5270
|
+
lst = [
|
5271
|
+
si
|
5272
|
+
for si in p.get_installed_versions(spec)
|
5273
|
+
if spec.contains(si)
|
5274
|
+
]
|
5275
|
+
if lst:
|
5276
|
+
print(f' {n}')
|
5277
|
+
for si in lst:
|
5278
|
+
print(f' {si}')
|
5279
|
+
|
5280
|
+
print()
|
5281
|
+
|
5282
|
+
print('installable:')
|
5283
|
+
for n, p in self._providers.items():
|
5284
|
+
lst = [
|
5285
|
+
si
|
5286
|
+
for si in p.get_installable_versions(spec)
|
5287
|
+
if spec.contains(si)
|
5288
|
+
]
|
5289
|
+
if lst:
|
5290
|
+
print(f' {n}')
|
5291
|
+
for si in lst:
|
5292
|
+
print(f' {si}')
|
5293
|
+
|
5294
|
+
|
5295
|
+
DEFAULT_INTERP_RESOLVER = InterpResolver([(p.name, p) for p in [
|
5296
|
+
# pyenv is preferred to system interpreters as it tends to have more support for things like tkinter
|
5297
|
+
PyenvInterpProvider(try_update=True),
|
5298
|
+
|
5299
|
+
RunningInterpProvider(),
|
5300
|
+
|
5301
|
+
SystemInterpProvider(),
|
5302
|
+
]])
|
5303
|
+
|
5304
|
+
|
5305
|
+
########################################
|
5306
|
+
# ../commands/interp.py
|
5307
|
+
|
5308
|
+
|
5309
|
+
##
|
5310
|
+
|
5311
|
+
|
5312
|
+
@dc.dataclass(frozen=True)
|
5313
|
+
class InterpCommand(Command['InterpCommand.Output']):
|
5314
|
+
spec: str
|
5315
|
+
install: bool = False
|
5316
|
+
|
5317
|
+
@dc.dataclass(frozen=True)
|
5318
|
+
class Output(Command.Output):
|
5319
|
+
exe: str
|
5320
|
+
version: str
|
5321
|
+
opts: InterpOpts
|
5322
|
+
|
5323
|
+
|
5324
|
+
##
|
5325
|
+
|
5326
|
+
|
5327
|
+
class InterpCommandExecutor(CommandExecutor[InterpCommand, InterpCommand.Output]):
|
5328
|
+
def execute(self, cmd: InterpCommand) -> InterpCommand.Output:
|
5329
|
+
i = InterpSpecifier.parse(check_not_none(cmd.spec))
|
5330
|
+
o = check_not_none(DEFAULT_INTERP_RESOLVER.resolve(i, install=cmd.install))
|
5331
|
+
return InterpCommand.Output(
|
5332
|
+
exe=o.exe,
|
5333
|
+
version=str(o.version.version),
|
5334
|
+
opts=o.version.opts,
|
5335
|
+
)
|
5336
|
+
|
5337
|
+
|
5338
|
+
########################################
|
5339
|
+
# ../commands/inject.py
|
5340
|
+
|
5341
|
+
|
5342
|
+
##
|
5343
|
+
|
5344
|
+
|
5345
|
+
def bind_command(
|
5346
|
+
command_cls: ta.Type[Command],
|
5347
|
+
executor_cls: ta.Optional[ta.Type[CommandExecutor]],
|
5348
|
+
) -> InjectorBindings:
|
5349
|
+
lst: ta.List[InjectorBindingOrBindings] = [
|
5350
|
+
inj.bind(CommandRegistration(command_cls), array=True),
|
5351
|
+
]
|
5352
|
+
|
5353
|
+
if executor_cls is not None:
|
5354
|
+
lst.extend([
|
5355
|
+
inj.bind(executor_cls, singleton=True),
|
5356
|
+
inj.bind(CommandExecutorRegistration(command_cls, executor_cls), array=True),
|
5357
|
+
])
|
5358
|
+
|
5359
|
+
return inj.as_bindings(*lst)
|
5360
|
+
|
5361
|
+
|
5362
|
+
##
|
5363
|
+
|
5364
|
+
|
5365
|
+
@dc.dataclass(frozen=True)
|
5366
|
+
class _FactoryCommandExecutor(CommandExecutor):
|
5367
|
+
factory: ta.Callable[[], CommandExecutor]
|
5368
|
+
|
5369
|
+
def execute(self, i: Command) -> Command.Output:
|
5370
|
+
return self.factory().execute(i)
|
5371
|
+
|
5372
|
+
|
5373
|
+
##
|
5374
|
+
|
5375
|
+
|
5376
|
+
def bind_commands(
|
5377
|
+
*,
|
5378
|
+
main_config: MainConfig,
|
5379
|
+
) -> InjectorBindings:
|
5380
|
+
lst: ta.List[InjectorBindingOrBindings] = [
|
5381
|
+
inj.bind_array(CommandRegistration),
|
5382
|
+
inj.bind_array_type(CommandRegistration, CommandRegistrations),
|
5383
|
+
|
5384
|
+
inj.bind_array(CommandExecutorRegistration),
|
5385
|
+
inj.bind_array_type(CommandExecutorRegistration, CommandExecutorRegistrations),
|
5386
|
+
|
5387
|
+
inj.bind(build_command_name_map, singleton=True),
|
5388
|
+
]
|
5389
|
+
|
5390
|
+
#
|
5391
|
+
|
5392
|
+
def provide_obj_marshaler_installer(cmds: CommandNameMap) -> ObjMarshalerInstaller:
|
5393
|
+
return ObjMarshalerInstaller(functools.partial(install_command_marshaling, cmds))
|
5394
|
+
|
5395
|
+
lst.append(inj.bind(provide_obj_marshaler_installer, array=True))
|
5396
|
+
|
5397
|
+
#
|
5398
|
+
|
5399
|
+
def provide_command_executor_map(
|
5400
|
+
injector: Injector,
|
5401
|
+
crs: CommandExecutorRegistrations,
|
5402
|
+
) -> CommandExecutorMap:
|
5403
|
+
dct: ta.Dict[ta.Type[Command], CommandExecutor] = {}
|
5404
|
+
|
5405
|
+
cr: CommandExecutorRegistration
|
5406
|
+
for cr in crs:
|
5407
|
+
if cr.command_cls in dct:
|
5408
|
+
raise KeyError(cr.command_cls)
|
5409
|
+
|
5410
|
+
factory = functools.partial(injector.provide, cr.executor_cls)
|
5411
|
+
if main_config.debug:
|
5412
|
+
ce = factory()
|
5413
|
+
else:
|
5414
|
+
ce = _FactoryCommandExecutor(factory)
|
5415
|
+
|
5416
|
+
dct[cr.command_cls] = ce
|
5417
|
+
|
5418
|
+
return CommandExecutorMap(dct)
|
5419
|
+
|
5420
|
+
lst.extend([
|
5421
|
+
inj.bind(provide_command_executor_map, singleton=True),
|
5422
|
+
|
5423
|
+
inj.bind(LocalCommandExecutor, singleton=True, eager=main_config.debug),
|
5424
|
+
])
|
5425
|
+
|
5426
|
+
#
|
5427
|
+
|
5428
|
+
command_cls: ta.Any
|
5429
|
+
executor_cls: ta.Any
|
5430
|
+
for command_cls, executor_cls in [
|
5431
|
+
(SubprocessCommand, SubprocessCommandExecutor),
|
5432
|
+
(InterpCommand, InterpCommandExecutor),
|
5433
|
+
]:
|
5434
|
+
lst.append(bind_command(command_cls, executor_cls))
|
5435
|
+
|
5436
|
+
#
|
5437
|
+
|
5438
|
+
return inj.as_bindings(*lst)
|
5439
|
+
|
5440
|
+
|
5441
|
+
########################################
|
5442
|
+
# ../deploy/inject.py
|
5443
|
+
|
5444
|
+
|
5445
|
+
def bind_deploy(
|
5446
|
+
) -> InjectorBindings:
|
5447
|
+
lst: ta.List[InjectorBindingOrBindings] = [
|
5448
|
+
bind_command(DeployCommand, DeployCommandExecutor),
|
5449
|
+
]
|
5450
|
+
|
5451
|
+
return inj.as_bindings(*lst)
|
5452
|
+
|
5453
|
+
|
5454
|
+
########################################
|
5455
|
+
# ../inject.py
|
5456
|
+
|
5457
|
+
|
5458
|
+
##
|
5459
|
+
|
5460
|
+
|
5461
|
+
def bind_main(
|
5462
|
+
*,
|
5463
|
+
main_config: MainConfig,
|
5464
|
+
remote_config: RemoteConfig,
|
5465
|
+
) -> InjectorBindings:
|
5466
|
+
lst: ta.List[InjectorBindingOrBindings] = [
|
5467
|
+
inj.bind(main_config),
|
3516
5468
|
|
3517
5469
|
bind_commands(
|
3518
5470
|
main_config=main_config,
|
@@ -3618,12 +5570,15 @@ def _main() -> None:
|
|
3618
5570
|
|
3619
5571
|
#
|
3620
5572
|
|
5573
|
+
msh = injector[ObjMarshalerManager]
|
5574
|
+
|
3621
5575
|
cmds: ta.List[Command] = []
|
5576
|
+
cmd: Command
|
3622
5577
|
for c in args.command:
|
3623
|
-
if c
|
3624
|
-
|
3625
|
-
|
3626
|
-
|
5578
|
+
if not c.startswith('{'):
|
5579
|
+
c = json.dumps({c: {}})
|
5580
|
+
cmd = msh.unmarshal_obj(json.loads(c), Command)
|
5581
|
+
cmds.append(cmd)
|
3627
5582
|
|
3628
5583
|
#
|
3629
5584
|
|
@@ -3643,9 +5598,13 @@ def _main() -> None:
|
|
3643
5598
|
ce = es.enter_context(injector[RemoteExecution].connect(tgt, bs)) # noqa
|
3644
5599
|
|
3645
5600
|
for cmd in cmds:
|
3646
|
-
r = ce.try_execute(
|
5601
|
+
r = ce.try_execute(
|
5602
|
+
cmd,
|
5603
|
+
log=log,
|
5604
|
+
omit_exc_object=True,
|
5605
|
+
)
|
3647
5606
|
|
3648
|
-
print(
|
5607
|
+
print(msh.marshal_obj(r, opts=ObjMarshalOptions(raw_bytes=True)))
|
3649
5608
|
|
3650
5609
|
|
3651
5610
|
if __name__ == '__main__':
|