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