ominfra 0.0.0.dev144__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/execution.py +1 -1
- ominfra/manage/commands/inject.py +7 -3
- 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 +180 -0
- ominfra/manage/main.py +16 -9
- ominfra/manage/remote/channel.py +13 -2
- ominfra/manage/remote/execution.py +66 -9
- ominfra/scripts/journald2aws.py +73 -53
- ominfra/scripts/manage.py +2272 -293
- ominfra/scripts/supervisor.py +114 -94
- ominfra/supervisor/dispatchers.py +3 -3
- ominfra/supervisor/http.py +6 -6
- ominfra/supervisor/inject.py +12 -12
- ominfra/supervisor/io.py +2 -2
- ominfra/supervisor/spawningimpl.py +2 -2
- ominfra/supervisor/supervisor.py +2 -2
- ominfra/supervisor/types.py +2 -2
- {ominfra-0.0.0.dev144.dist-info → ominfra-0.0.0.dev146.dist-info}/METADATA +3 -3
- {ominfra-0.0.0.dev144.dist-info → ominfra-0.0.0.dev146.dist-info}/RECORD +25 -23
- {ominfra-0.0.0.dev144.dist-info → ominfra-0.0.0.dev146.dist-info}/LICENSE +0 -0
- {ominfra-0.0.0.dev144.dist-info → ominfra-0.0.0.dev146.dist-info}/WHEEL +0 -0
- {ominfra-0.0.0.dev144.dist-info → ominfra-0.0.0.dev146.dist-info}/entry_points.txt +0 -0
- {ominfra-0.0.0.dev144.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:
|
@@ -2328,23 +3275,24 @@ TODO:
|
|
2328
3275
|
@dc.dataclass(frozen=True)
|
2329
3276
|
class ObjMarshalOptions:
|
2330
3277
|
raw_bytes: bool = False
|
3278
|
+
nonstrict_dataclasses: bool = False
|
2331
3279
|
|
2332
3280
|
|
2333
3281
|
class ObjMarshaler(abc.ABC):
|
2334
3282
|
@abc.abstractmethod
|
2335
|
-
def marshal(self, o: ta.Any,
|
3283
|
+
def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
2336
3284
|
raise NotImplementedError
|
2337
3285
|
|
2338
3286
|
@abc.abstractmethod
|
2339
|
-
def unmarshal(self, o: ta.Any,
|
3287
|
+
def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
2340
3288
|
raise NotImplementedError
|
2341
3289
|
|
2342
3290
|
|
2343
3291
|
class NopObjMarshaler(ObjMarshaler):
|
2344
|
-
def marshal(self, o: ta.Any,
|
3292
|
+
def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
2345
3293
|
return o
|
2346
3294
|
|
2347
|
-
def unmarshal(self, o: ta.Any,
|
3295
|
+
def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
2348
3296
|
return o
|
2349
3297
|
|
2350
3298
|
|
@@ -2352,29 +3300,29 @@ class NopObjMarshaler(ObjMarshaler):
|
|
2352
3300
|
class ProxyObjMarshaler(ObjMarshaler):
|
2353
3301
|
m: ta.Optional[ObjMarshaler] = None
|
2354
3302
|
|
2355
|
-
def marshal(self, o: ta.Any,
|
2356
|
-
return check_not_none(self.m).marshal(o,
|
3303
|
+
def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
3304
|
+
return check_not_none(self.m).marshal(o, ctx)
|
2357
3305
|
|
2358
|
-
def unmarshal(self, o: ta.Any,
|
2359
|
-
return check_not_none(self.m).unmarshal(o,
|
3306
|
+
def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
3307
|
+
return check_not_none(self.m).unmarshal(o, ctx)
|
2360
3308
|
|
2361
3309
|
|
2362
3310
|
@dc.dataclass(frozen=True)
|
2363
3311
|
class CastObjMarshaler(ObjMarshaler):
|
2364
3312
|
ty: type
|
2365
3313
|
|
2366
|
-
def marshal(self, o: ta.Any,
|
3314
|
+
def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
2367
3315
|
return o
|
2368
3316
|
|
2369
|
-
def unmarshal(self, o: ta.Any,
|
3317
|
+
def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
2370
3318
|
return self.ty(o)
|
2371
3319
|
|
2372
3320
|
|
2373
3321
|
class DynamicObjMarshaler(ObjMarshaler):
|
2374
|
-
def marshal(self, o: ta.Any,
|
2375
|
-
return marshal_obj(o)
|
3322
|
+
def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
3323
|
+
return ctx.manager.marshal_obj(o, opts=ctx.options)
|
2376
3324
|
|
2377
|
-
def unmarshal(self, o: ta.Any,
|
3325
|
+
def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
2378
3326
|
return o
|
2379
3327
|
|
2380
3328
|
|
@@ -2382,10 +3330,10 @@ class DynamicObjMarshaler(ObjMarshaler):
|
|
2382
3330
|
class Base64ObjMarshaler(ObjMarshaler):
|
2383
3331
|
ty: type
|
2384
3332
|
|
2385
|
-
def marshal(self, o: ta.Any,
|
3333
|
+
def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
2386
3334
|
return base64.b64encode(o).decode('ascii')
|
2387
3335
|
|
2388
|
-
def unmarshal(self, o: ta.Any,
|
3336
|
+
def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
2389
3337
|
return self.ty(base64.b64decode(o))
|
2390
3338
|
|
2391
3339
|
|
@@ -2393,25 +3341,25 @@ class Base64ObjMarshaler(ObjMarshaler):
|
|
2393
3341
|
class BytesSwitchedObjMarshaler(ObjMarshaler):
|
2394
3342
|
m: ObjMarshaler
|
2395
3343
|
|
2396
|
-
def marshal(self, o: ta.Any,
|
2397
|
-
if
|
3344
|
+
def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
3345
|
+
if ctx.options.raw_bytes:
|
2398
3346
|
return o
|
2399
|
-
return self.m.marshal(o,
|
3347
|
+
return self.m.marshal(o, ctx)
|
2400
3348
|
|
2401
|
-
def unmarshal(self, o: ta.Any,
|
2402
|
-
if
|
3349
|
+
def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
3350
|
+
if ctx.options.raw_bytes:
|
2403
3351
|
return o
|
2404
|
-
return self.m.unmarshal(o,
|
3352
|
+
return self.m.unmarshal(o, ctx)
|
2405
3353
|
|
2406
3354
|
|
2407
3355
|
@dc.dataclass(frozen=True)
|
2408
3356
|
class EnumObjMarshaler(ObjMarshaler):
|
2409
3357
|
ty: type
|
2410
3358
|
|
2411
|
-
def marshal(self, o: ta.Any,
|
3359
|
+
def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
2412
3360
|
return o.name
|
2413
3361
|
|
2414
|
-
def unmarshal(self, o: ta.Any,
|
3362
|
+
def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
2415
3363
|
return self.ty.__members__[o] # type: ignore
|
2416
3364
|
|
2417
3365
|
|
@@ -2419,15 +3367,15 @@ class EnumObjMarshaler(ObjMarshaler):
|
|
2419
3367
|
class OptionalObjMarshaler(ObjMarshaler):
|
2420
3368
|
item: ObjMarshaler
|
2421
3369
|
|
2422
|
-
def marshal(self, o: ta.Any,
|
3370
|
+
def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
2423
3371
|
if o is None:
|
2424
3372
|
return None
|
2425
|
-
return self.item.marshal(o,
|
3373
|
+
return self.item.marshal(o, ctx)
|
2426
3374
|
|
2427
|
-
def unmarshal(self, o: ta.Any,
|
3375
|
+
def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
2428
3376
|
if o is None:
|
2429
3377
|
return None
|
2430
|
-
return self.item.unmarshal(o,
|
3378
|
+
return self.item.unmarshal(o, ctx)
|
2431
3379
|
|
2432
3380
|
|
2433
3381
|
@dc.dataclass(frozen=True)
|
@@ -2436,11 +3384,11 @@ class MappingObjMarshaler(ObjMarshaler):
|
|
2436
3384
|
km: ObjMarshaler
|
2437
3385
|
vm: ObjMarshaler
|
2438
3386
|
|
2439
|
-
def marshal(self, o: ta.Any,
|
2440
|
-
return {self.km.marshal(k,
|
3387
|
+
def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
3388
|
+
return {self.km.marshal(k, ctx): self.vm.marshal(v, ctx) for k, v in o.items()}
|
2441
3389
|
|
2442
|
-
def unmarshal(self, o: ta.Any,
|
2443
|
-
return self.ty((self.km.unmarshal(k,
|
3390
|
+
def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
3391
|
+
return self.ty((self.km.unmarshal(k, ctx), self.vm.unmarshal(v, ctx)) for k, v in o.items())
|
2444
3392
|
|
2445
3393
|
|
2446
3394
|
@dc.dataclass(frozen=True)
|
@@ -2448,11 +3396,11 @@ class IterableObjMarshaler(ObjMarshaler):
|
|
2448
3396
|
ty: type
|
2449
3397
|
item: ObjMarshaler
|
2450
3398
|
|
2451
|
-
def marshal(self, o: ta.Any,
|
2452
|
-
return [self.item.marshal(e,
|
3399
|
+
def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
3400
|
+
return [self.item.marshal(e, ctx) for e in o]
|
2453
3401
|
|
2454
|
-
def unmarshal(self, o: ta.Any,
|
2455
|
-
return self.ty(self.item.unmarshal(e,
|
3402
|
+
def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
3403
|
+
return self.ty(self.item.unmarshal(e, ctx) for e in o)
|
2456
3404
|
|
2457
3405
|
|
2458
3406
|
@dc.dataclass(frozen=True)
|
@@ -2461,11 +3409,18 @@ class DataclassObjMarshaler(ObjMarshaler):
|
|
2461
3409
|
fs: ta.Mapping[str, ObjMarshaler]
|
2462
3410
|
nonstrict: bool = False
|
2463
3411
|
|
2464
|
-
def marshal(self, o: ta.Any,
|
2465
|
-
return {
|
3412
|
+
def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
3413
|
+
return {
|
3414
|
+
k: m.marshal(getattr(o, k), ctx)
|
3415
|
+
for k, m in self.fs.items()
|
3416
|
+
}
|
2466
3417
|
|
2467
|
-
def unmarshal(self, o: ta.Any,
|
2468
|
-
return self.ty(**{
|
3418
|
+
def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
3419
|
+
return self.ty(**{
|
3420
|
+
k: self.fs[k].unmarshal(v, ctx)
|
3421
|
+
for k, v in o.items()
|
3422
|
+
if not (self.nonstrict or ctx.options.nonstrict_dataclasses) or k in self.fs
|
3423
|
+
})
|
2469
3424
|
|
2470
3425
|
|
2471
3426
|
@dc.dataclass(frozen=True)
|
@@ -2485,50 +3440,50 @@ class PolymorphicObjMarshaler(ObjMarshaler):
|
|
2485
3440
|
{i.tag: i for i in impls},
|
2486
3441
|
)
|
2487
3442
|
|
2488
|
-
def marshal(self, o: ta.Any,
|
3443
|
+
def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
2489
3444
|
impl = self.impls_by_ty[type(o)]
|
2490
|
-
return {impl.tag: impl.m.marshal(o,
|
3445
|
+
return {impl.tag: impl.m.marshal(o, ctx)}
|
2491
3446
|
|
2492
|
-
def unmarshal(self, o: ta.Any,
|
3447
|
+
def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
2493
3448
|
[(t, v)] = o.items()
|
2494
3449
|
impl = self.impls_by_tag[t]
|
2495
|
-
return impl.m.unmarshal(v,
|
3450
|
+
return impl.m.unmarshal(v, ctx)
|
2496
3451
|
|
2497
3452
|
|
2498
3453
|
@dc.dataclass(frozen=True)
|
2499
3454
|
class DatetimeObjMarshaler(ObjMarshaler):
|
2500
3455
|
ty: type
|
2501
3456
|
|
2502
|
-
def marshal(self, o: ta.Any,
|
3457
|
+
def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
2503
3458
|
return o.isoformat()
|
2504
3459
|
|
2505
|
-
def unmarshal(self, o: ta.Any,
|
3460
|
+
def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
2506
3461
|
return self.ty.fromisoformat(o) # type: ignore
|
2507
3462
|
|
2508
3463
|
|
2509
3464
|
class DecimalObjMarshaler(ObjMarshaler):
|
2510
|
-
def marshal(self, o: ta.Any,
|
3465
|
+
def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
2511
3466
|
return str(check_isinstance(o, decimal.Decimal))
|
2512
3467
|
|
2513
|
-
def unmarshal(self, v: ta.Any,
|
3468
|
+
def unmarshal(self, v: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
2514
3469
|
return decimal.Decimal(check_isinstance(v, str))
|
2515
3470
|
|
2516
3471
|
|
2517
3472
|
class FractionObjMarshaler(ObjMarshaler):
|
2518
|
-
def marshal(self, o: ta.Any,
|
3473
|
+
def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
2519
3474
|
fr = check_isinstance(o, fractions.Fraction)
|
2520
3475
|
return [fr.numerator, fr.denominator]
|
2521
3476
|
|
2522
|
-
def unmarshal(self, v: ta.Any,
|
3477
|
+
def unmarshal(self, v: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
2523
3478
|
num, denom = check_isinstance(v, list)
|
2524
3479
|
return fractions.Fraction(num, denom)
|
2525
3480
|
|
2526
3481
|
|
2527
3482
|
class UuidObjMarshaler(ObjMarshaler):
|
2528
|
-
def marshal(self, o: ta.Any,
|
3483
|
+
def marshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
2529
3484
|
return str(o)
|
2530
3485
|
|
2531
|
-
def unmarshal(self, o: ta.Any,
|
3486
|
+
def unmarshal(self, o: ta.Any, ctx: 'ObjMarshalContext') -> ta.Any:
|
2532
3487
|
return uuid.UUID(o)
|
2533
3488
|
|
2534
3489
|
|
@@ -2689,6 +3644,12 @@ class ObjMarshalerManager:
|
|
2689
3644
|
|
2690
3645
|
#
|
2691
3646
|
|
3647
|
+
def _make_context(self, opts: ta.Optional[ObjMarshalOptions]) -> 'ObjMarshalContext':
|
3648
|
+
return ObjMarshalContext(
|
3649
|
+
options=opts or self._default_options,
|
3650
|
+
manager=self,
|
3651
|
+
)
|
3652
|
+
|
2692
3653
|
def marshal_obj(
|
2693
3654
|
self,
|
2694
3655
|
o: ta.Any,
|
@@ -2696,7 +3657,7 @@ class ObjMarshalerManager:
|
|
2696
3657
|
opts: ta.Optional[ObjMarshalOptions] = None,
|
2697
3658
|
) -> ta.Any:
|
2698
3659
|
m = self.get_obj_marshaler(ty if ty is not None else type(o))
|
2699
|
-
return m.marshal(o,
|
3660
|
+
return m.marshal(o, self._make_context(opts))
|
2700
3661
|
|
2701
3662
|
def unmarshal_obj(
|
2702
3663
|
self,
|
@@ -2705,7 +3666,7 @@ class ObjMarshalerManager:
|
|
2705
3666
|
opts: ta.Optional[ObjMarshalOptions] = None,
|
2706
3667
|
) -> T:
|
2707
3668
|
m = self.get_obj_marshaler(ty)
|
2708
|
-
return m.unmarshal(o,
|
3669
|
+
return m.unmarshal(o, self._make_context(opts))
|
2709
3670
|
|
2710
3671
|
def roundtrip_obj(
|
2711
3672
|
self,
|
@@ -2720,6 +3681,12 @@ class ObjMarshalerManager:
|
|
2720
3681
|
return u
|
2721
3682
|
|
2722
3683
|
|
3684
|
+
@dc.dataclass(frozen=True)
|
3685
|
+
class ObjMarshalContext:
|
3686
|
+
options: ObjMarshalOptions
|
3687
|
+
manager: ObjMarshalerManager
|
3688
|
+
|
3689
|
+
|
2723
3690
|
##
|
2724
3691
|
|
2725
3692
|
|
@@ -2750,34 +3717,125 @@ def check_runtime_version() -> None:
|
|
2750
3717
|
|
2751
3718
|
|
2752
3719
|
########################################
|
2753
|
-
#
|
3720
|
+
# ../../../omdev/interp/types.py
|
2754
3721
|
|
2755
3722
|
|
2756
|
-
|
2757
|
-
|
2758
|
-
|
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
|
+
])
|
2759
3728
|
|
2760
|
-
|
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
|
+
)
|
2761
3732
|
|
2762
3733
|
|
2763
|
-
|
2764
|
-
|
3734
|
+
@dc.dataclass(frozen=True)
|
3735
|
+
class InterpOpts:
|
3736
|
+
threaded: bool = False
|
3737
|
+
debug: bool = False
|
2765
3738
|
|
3739
|
+
def __str__(self) -> str:
|
3740
|
+
return ''.join(g for a, g in INTERP_OPT_GLYPHS_BY_ATTR.items() if getattr(self, a))
|
2766
3741
|
|
2767
|
-
|
3742
|
+
@classmethod
|
3743
|
+
def parse(cls, s: str) -> 'InterpOpts':
|
3744
|
+
return cls(**{INTERP_OPT_ATTRS_BY_GLYPH[g]: True for g in s})
|
2768
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)
|
2769
3752
|
|
2770
|
-
class CommandExecutionService(CommandExecutor):
|
2771
|
-
def __init__(
|
2772
|
-
self,
|
2773
|
-
*,
|
2774
|
-
command_executors: CommandExecutorMap,
|
2775
|
-
) -> None:
|
2776
|
-
super().__init__()
|
2777
3753
|
|
2778
|
-
|
3754
|
+
@dc.dataclass(frozen=True)
|
3755
|
+
class InterpVersion:
|
3756
|
+
version: Version
|
3757
|
+
opts: InterpOpts
|
2779
3758
|
|
2780
|
-
def
|
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:
|
2781
3839
|
ce: CommandExecutor = self._command_executors[type(cmd)]
|
2782
3840
|
return ce.execute(cmd)
|
2783
3841
|
|
@@ -2826,6 +3884,8 @@ class DeployCommand(Command['DeployCommand.Output']):
|
|
2826
3884
|
|
2827
3885
|
class DeployCommandExecutor(CommandExecutor[DeployCommand, DeployCommand.Output]):
|
2828
3886
|
def execute(self, cmd: DeployCommand) -> DeployCommand.Output:
|
3887
|
+
log.info('Deploying!')
|
3888
|
+
|
2829
3889
|
return DeployCommand.Output()
|
2830
3890
|
|
2831
3891
|
|
@@ -2859,10 +3919,12 @@ class RemoteChannel:
|
|
2859
3919
|
self._output = output
|
2860
3920
|
self._msh = msh
|
2861
3921
|
|
3922
|
+
self._lock = threading.RLock()
|
3923
|
+
|
2862
3924
|
def set_marshaler(self, msh: ObjMarshalerManager) -> None:
|
2863
3925
|
self._msh = msh
|
2864
3926
|
|
2865
|
-
def
|
3927
|
+
def _send_obj(self, o: ta.Any, ty: ta.Any = None) -> None:
|
2866
3928
|
j = json_dumps_compact(self._msh.marshal_obj(o, ty))
|
2867
3929
|
d = j.encode('utf-8')
|
2868
3930
|
|
@@ -2870,7 +3932,11 @@ class RemoteChannel:
|
|
2870
3932
|
self._output.write(d)
|
2871
3933
|
self._output.flush()
|
2872
3934
|
|
2873
|
-
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]:
|
2874
3940
|
d = self._input.read(4)
|
2875
3941
|
if not d:
|
2876
3942
|
return None
|
@@ -2885,6 +3951,10 @@ class RemoteChannel:
|
|
2885
3951
|
j = json.loads(d.decode('utf-8'))
|
2886
3952
|
return self._msh.unmarshal_obj(j, ty)
|
2887
3953
|
|
3954
|
+
def recv_obj(self, ty: ta.Type[T]) -> ta.Optional[T]:
|
3955
|
+
with self._lock:
|
3956
|
+
return self._recv_obj(ty)
|
3957
|
+
|
2888
3958
|
|
2889
3959
|
########################################
|
2890
3960
|
# ../../../omlish/lite/subprocesses.py
|
@@ -3020,6 +4090,102 @@ def subprocess_close(
|
|
3020
4090
|
proc.wait(timeout)
|
3021
4091
|
|
3022
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
|
+
|
3023
4189
|
########################################
|
3024
4190
|
# ../commands/subprocess.py
|
3025
4191
|
|
@@ -3042,8 +4208,7 @@ class SubprocessCommand(Command['SubprocessCommand.Output']):
|
|
3042
4208
|
timeout: ta.Optional[float] = None
|
3043
4209
|
|
3044
4210
|
def __post_init__(self) -> None:
|
3045
|
-
|
3046
|
-
raise TypeError(self.cmd)
|
4211
|
+
check_not_isinstance(self.cmd, str)
|
3047
4212
|
|
3048
4213
|
@dc.dataclass(frozen=True)
|
3049
4214
|
class Output(Command.Output):
|
@@ -3180,111 +4345,97 @@ class RemoteSpawning:
|
|
3180
4345
|
|
3181
4346
|
|
3182
4347
|
########################################
|
3183
|
-
#
|
4348
|
+
# ../../../omdev/interp/providers.py
|
4349
|
+
"""
|
4350
|
+
TODO:
|
4351
|
+
- backends
|
4352
|
+
- local builds
|
4353
|
+
- deadsnakes?
|
4354
|
+
- uv
|
4355
|
+
- loose versions
|
4356
|
+
"""
|
3184
4357
|
|
3185
4358
|
|
3186
4359
|
##
|
3187
4360
|
|
3188
4361
|
|
3189
|
-
|
3190
|
-
|
3191
|
-
executor_cls: ta.Optional[ta.Type[CommandExecutor]],
|
3192
|
-
) -> InjectorBindings:
|
3193
|
-
lst: ta.List[InjectorBindingOrBindings] = [
|
3194
|
-
inj.bind(CommandRegistration(command_cls), array=True),
|
3195
|
-
]
|
3196
|
-
|
3197
|
-
if executor_cls is not None:
|
3198
|
-
lst.extend([
|
3199
|
-
inj.bind(executor_cls, singleton=True),
|
3200
|
-
inj.bind(CommandExecutorRegistration(command_cls, executor_cls), array=True),
|
3201
|
-
])
|
3202
|
-
|
3203
|
-
return inj.as_bindings(*lst)
|
4362
|
+
class InterpProvider(abc.ABC):
|
4363
|
+
name: ta.ClassVar[str]
|
3204
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)]))
|
3205
4372
|
|
3206
|
-
|
4373
|
+
@abc.abstractmethod
|
4374
|
+
def get_installed_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
|
4375
|
+
raise NotImplementedError
|
3207
4376
|
|
4377
|
+
@abc.abstractmethod
|
4378
|
+
def get_installed_version(self, version: InterpVersion) -> Interp:
|
4379
|
+
raise NotImplementedError
|
3208
4380
|
|
3209
|
-
|
3210
|
-
|
3211
|
-
factory: ta.Callable[[], CommandExecutor]
|
4381
|
+
def get_installable_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
|
4382
|
+
return []
|
3212
4383
|
|
3213
|
-
def
|
3214
|
-
|
4384
|
+
def install_version(self, version: InterpVersion) -> Interp:
|
4385
|
+
raise TypeError
|
3215
4386
|
|
3216
4387
|
|
3217
4388
|
##
|
3218
4389
|
|
3219
4390
|
|
3220
|
-
|
3221
|
-
|
3222
|
-
|
3223
|
-
)
|
3224
|
-
|
3225
|
-
|
3226
|
-
|
3227
|
-
|
3228
|
-
|
3229
|
-
|
3230
|
-
|
3231
|
-
|
3232
|
-
|
3233
|
-
|
3234
|
-
|
3235
|
-
|
3236
|
-
def provide_obj_marshaler_installer(cmds: CommandNameMap) -> ObjMarshalerInstaller:
|
3237
|
-
return ObjMarshalerInstaller(functools.partial(install_command_marshaling, cmds))
|
3238
|
-
|
3239
|
-
lst.append(inj.bind(provide_obj_marshaler_installer, array=True))
|
3240
|
-
|
3241
|
-
#
|
3242
|
-
|
3243
|
-
def provide_command_executor_map(
|
3244
|
-
injector: Injector,
|
3245
|
-
crs: CommandExecutorRegistrations,
|
3246
|
-
) -> CommandExecutorMap:
|
3247
|
-
dct: ta.Dict[ta.Type[Command], CommandExecutor] = {}
|
3248
|
-
|
3249
|
-
cr: CommandExecutorRegistration
|
3250
|
-
for cr in crs:
|
3251
|
-
if cr.command_cls in dct:
|
3252
|
-
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
|
+
)
|
3253
4406
|
|
3254
|
-
factory = functools.partial(injector.provide, cr.executor_cls)
|
3255
|
-
if main_config.debug:
|
3256
|
-
ce = factory()
|
3257
|
-
else:
|
3258
|
-
ce = _FactoryCommandExecutor(factory)
|
3259
4407
|
|
3260
|
-
|
4408
|
+
########################################
|
4409
|
+
# ../remote/execution.py
|
3261
4410
|
|
3262
|
-
return CommandExecutorMap(dct)
|
3263
4411
|
|
3264
|
-
|
3265
|
-
inj.bind(provide_command_executor_map, singleton=True),
|
4412
|
+
##
|
3266
4413
|
|
3267
|
-
inj.bind(CommandExecutionService, singleton=True, eager=main_config.debug),
|
3268
|
-
inj.bind(CommandExecutor, to_key=CommandExecutionService),
|
3269
|
-
])
|
3270
4414
|
|
3271
|
-
|
4415
|
+
class _RemoteExecutionLogHandler(logging.Handler):
|
4416
|
+
def __init__(self, fn: ta.Callable[[str], None]) -> None:
|
4417
|
+
super().__init__()
|
4418
|
+
self._fn = fn
|
3272
4419
|
|
3273
|
-
|
3274
|
-
(
|
3275
|
-
|
3276
|
-
lst.append(bind_command(command_cls, executor_cls))
|
4420
|
+
def emit(self, record):
|
4421
|
+
msg = self.format(record)
|
4422
|
+
self._fn(msg)
|
3277
4423
|
|
3278
|
-
#
|
3279
4424
|
|
3280
|
-
|
4425
|
+
@dc.dataclass(frozen=True)
|
4426
|
+
class _RemoteExecutionRequest:
|
4427
|
+
c: Command
|
3281
4428
|
|
3282
4429
|
|
3283
|
-
|
3284
|
-
|
4430
|
+
@dc.dataclass(frozen=True)
|
4431
|
+
class _RemoteExecutionLog:
|
4432
|
+
s: str
|
3285
4433
|
|
3286
4434
|
|
3287
|
-
|
4435
|
+
@dc.dataclass(frozen=True)
|
4436
|
+
class _RemoteExecutionResponse:
|
4437
|
+
r: ta.Optional[CommandOutputOrExceptionData] = None
|
4438
|
+
l: ta.Optional[_RemoteExecutionLog] = None
|
3288
4439
|
|
3289
4440
|
|
3290
4441
|
def _remote_execution_main() -> None:
|
@@ -3304,20 +4455,44 @@ def _remote_execution_main() -> None:
|
|
3304
4455
|
|
3305
4456
|
chan.set_marshaler(injector[ObjMarshalerManager])
|
3306
4457
|
|
3307
|
-
|
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
|
+
|
4473
|
+
ce = injector[LocalCommandExecutor]
|
3308
4474
|
|
3309
4475
|
while True:
|
3310
|
-
|
3311
|
-
if
|
4476
|
+
req = chan.recv_obj(_RemoteExecutionRequest)
|
4477
|
+
if req is None:
|
3312
4478
|
break
|
3313
4479
|
|
4480
|
+
with log_lock:
|
4481
|
+
send_logs = True
|
4482
|
+
|
3314
4483
|
r = ce.try_execute(
|
3315
|
-
|
4484
|
+
req.c,
|
3316
4485
|
log=log,
|
3317
4486
|
omit_exc_object=True,
|
3318
4487
|
)
|
3319
4488
|
|
3320
|
-
|
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
|
+
)))
|
3321
4496
|
|
3322
4497
|
|
3323
4498
|
##
|
@@ -3335,12 +4510,17 @@ class RemoteCommandExecutor(CommandExecutor):
|
|
3335
4510
|
self._chan = chan
|
3336
4511
|
|
3337
4512
|
def _remote_execute(self, cmd: Command) -> CommandOutputOrException:
|
3338
|
-
self._chan.send_obj(cmd
|
4513
|
+
self._chan.send_obj(_RemoteExecutionRequest(cmd))
|
3339
4514
|
|
3340
|
-
|
3341
|
-
|
4515
|
+
while True:
|
4516
|
+
if (r := self._chan.recv_obj(_RemoteExecutionResponse)) is None:
|
4517
|
+
raise EOFError
|
3342
4518
|
|
3343
|
-
|
4519
|
+
if r.l is not None:
|
4520
|
+
log.info(r.l.s)
|
4521
|
+
|
4522
|
+
if r.r is not None:
|
4523
|
+
return r.r
|
3344
4524
|
|
3345
4525
|
# @ta.override
|
3346
4526
|
def execute(self, cmd: Command) -> Command.Output:
|
@@ -3446,62 +4626,854 @@ class RemoteExecution:
|
|
3446
4626
|
|
3447
4627
|
|
3448
4628
|
########################################
|
3449
|
-
#
|
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
|
+
"""
|
3450
4641
|
|
3451
4642
|
|
3452
|
-
|
3453
|
-
) -> InjectorBindings:
|
3454
|
-
lst: ta.List[InjectorBindingOrBindings] = [
|
3455
|
-
bind_command(DeployCommand, DeployCommandExecutor),
|
3456
|
-
]
|
4643
|
+
##
|
3457
4644
|
|
3458
|
-
return inj.as_bindings(*lst)
|
3459
4645
|
|
4646
|
+
class Pyenv:
|
3460
4647
|
|
3461
|
-
|
3462
|
-
|
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}')
|
3463
4655
|
|
4656
|
+
super().__init__()
|
3464
4657
|
|
3465
|
-
|
3466
|
-
*,
|
3467
|
-
remote_config: RemoteConfig,
|
3468
|
-
) -> InjectorBindings:
|
3469
|
-
lst: ta.List[InjectorBindingOrBindings] = [
|
3470
|
-
inj.bind(remote_config),
|
4658
|
+
self._root_kw = root
|
3471
4659
|
|
3472
|
-
|
4660
|
+
@cached_nullary
|
4661
|
+
def root(self) -> ta.Optional[str]:
|
4662
|
+
if self._root_kw is not None:
|
4663
|
+
return self._root_kw
|
3473
4664
|
|
3474
|
-
|
3475
|
-
|
4665
|
+
if shutil.which('pyenv'):
|
4666
|
+
return subprocess_check_output_str('pyenv', 'root')
|
3476
4667
|
|
3477
|
-
|
3478
|
-
|
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
|
3479
4671
|
|
3480
|
-
|
4672
|
+
return None
|
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
|
3481
4690
|
|
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
|
3482
4704
|
|
3483
|
-
|
3484
|
-
|
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
|
3485
4712
|
|
3486
4713
|
|
3487
4714
|
##
|
3488
4715
|
|
3489
4716
|
|
3490
|
-
|
3491
|
-
|
3492
|
-
|
3493
|
-
|
3494
|
-
|
3495
|
-
|
3496
|
-
|
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
|
+
)
|
3497
4733
|
|
3498
|
-
bind_commands(
|
3499
|
-
main_config=main_config,
|
3500
|
-
),
|
3501
4734
|
|
3502
|
-
|
3503
|
-
|
3504
|
-
|
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,
|
5476
|
+
),
|
3505
5477
|
|
3506
5478
|
bind_deploy(),
|
3507
5479
|
]
|
@@ -3599,12 +5571,15 @@ def _main() -> None:
|
|
3599
5571
|
|
3600
5572
|
#
|
3601
5573
|
|
5574
|
+
msh = injector[ObjMarshalerManager]
|
5575
|
+
|
3602
5576
|
cmds: ta.List[Command] = []
|
5577
|
+
cmd: Command
|
3603
5578
|
for c in args.command:
|
3604
|
-
if c
|
3605
|
-
|
3606
|
-
|
3607
|
-
|
5579
|
+
if not c.startswith('{'):
|
5580
|
+
c = json.dumps({c: {}})
|
5581
|
+
cmd = msh.unmarshal_obj(json.loads(c), Command)
|
5582
|
+
cmds.append(cmd)
|
3608
5583
|
|
3609
5584
|
#
|
3610
5585
|
|
@@ -3612,7 +5587,7 @@ def _main() -> None:
|
|
3612
5587
|
ce: CommandExecutor
|
3613
5588
|
|
3614
5589
|
if args.local:
|
3615
|
-
ce = injector[
|
5590
|
+
ce = injector[LocalCommandExecutor]
|
3616
5591
|
|
3617
5592
|
else:
|
3618
5593
|
tgt = RemoteSpawning.Target(
|
@@ -3624,9 +5599,13 @@ def _main() -> None:
|
|
3624
5599
|
ce = es.enter_context(injector[RemoteExecution].connect(tgt, bs)) # noqa
|
3625
5600
|
|
3626
5601
|
for cmd in cmds:
|
3627
|
-
r = ce.try_execute(
|
5602
|
+
r = ce.try_execute(
|
5603
|
+
cmd,
|
5604
|
+
log=log,
|
5605
|
+
omit_exc_object=True,
|
5606
|
+
)
|
3628
5607
|
|
3629
|
-
print(
|
5608
|
+
print(msh.marshal_obj(r, opts=ObjMarshalOptions(raw_bytes=True)))
|
3630
5609
|
|
3631
5610
|
|
3632
5611
|
if __name__ == '__main__':
|