ominfra 0.0.0.dev145__py3-none-any.whl → 0.0.0.dev146__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 +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__':
|